안녕하세요. 오늘은 Open API Specification 활용하기 마지막편입니다. 오늘 알아볼 내용은 3편에서 자동으로 만든 Open API Specification yaml을 기반으로 자동으로 retrofit2 코드를 만들어보도록 하겠습니다.
환경
Client의 환경은 이전 프로젝트와 동일하게 Spring Boot, Kotlin 환경으로 진행하도록 하겠습니다.
- kotlin 1.6.21
- Spring Boot 2.7.3
- gradle kotlin dsl 7.5
yaml 파일
자동으로 만들어낼 client Open API Specification 파일은 아래와 같습니다. 아래 파일은 이전 시간 3편에서 자동으로 만들어낸 yaml 파일과 동일한 파일입니다. 3개의 API Operation을 가지고 2개의 DTO Model을 가지고 있는 파일입니다.
openapi: 3.0.1
info:
title: OpenAPI definition
version: v0
servers:
- url: http://localhost:8080
description: Generated server url
paths:
/api/open/:
get:
tags:
- open-api-controller
operationId: findResponses
responses:
"200":
description: OK
content:
'*/*':
schema:
type: array
items:
$ref: '#/components/schemas/OpenApiResponse'
put:
tags:
- open-api-controller
operationId: createBook
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/OpenApiRequest'
required: true
responses:
"200":
description: OK
content:
'*/*':
schema:
$ref: '#/components/schemas/OpenApiResponse'
/api/open/{id}:
get:
tags:
- open-api-controller
operationId: findById
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
responses:
"200":
description: OK
content:
'*/*':
schema:
$ref: '#/components/schemas/OpenApiResponse'
components:
schemas:
OpenApiRequest:
type: object
properties:
string:
type: string
number:
type: integer
format: int32
number2:
type: integer
format: int64
number3:
type: number
format: double
time:
type: string
format: date-time
OpenApiResponse:
type: object
properties:
string:
type: string
number:
type: integer
format: int32
number2:
type: integer
format: int64
number3:
type: number
format: double
time:
type: string
format: date-time
build.gradle.kts
yaml 코드를 openapi generator를 통해서 자동으로 생성합니다. 여기서는 gradle kotlin dsl을 통해서 진행해보도록 하겠습니다. maven, cli 등 다양한 방법을 제공하고 있으니 편한 도구를 선택하시면 좋을것 같습니다.
plugins {
id("org.openapi.generator") version "6.0.1"
}
gradle 에서 client code generator를 하기 위해서는 org.openapi.generator
플러그인을 이용할 수 있습니다.
dependencies {
// for retrofit2 client
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
implementation("com.squareup.retrofit2:converter-scalars:2.9.0")
implementation("com.squareup.moshi:moshi-kotlin:1.12.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
}
retrofit2 코드를 자동으로 만들어내는것이기 때문에 이와 관련된 의존성을 위처럼 함께 가져옵니다.
openApiGenerate {
generatorName.set("kotlin")
inputSpec.set("$rootDir/docs/swagger.yaml")
outputDir.set("$buildDir/classes/generated")
apiPackage.set("org.openapi.example.api")
invokerPackage.set("org.openapi.example.invoker")
modelPackage.set("org.openapi.example.model")
configOptions.set(mapOf(
"dateLibrary" to "java8"
))
additionalProperties.set(mapOf(
"library" to "jvm-retrofit2"
))
}
그리고 openApiGenerate task의 옵션을 정의하여줍니다. 각 정의되는 내용은 open api generator 문서를 참고하시면 더 많은 정보를 얻어가실 수 있습니다. 이렇게 task를 정의한 후 task를 실행해보도록 하겠습니다. task 실행은 gradle openApiGenerate
를 통해 할 수 있습니다.
generated Class
gradle에서 task가 실행이 되고 아래처럼 kotlin 기반의 retfofit2 client 코드가 자동으로 생성되었습니다.
interface OpenApiControllerApi {
/**
*
*
* Responses:
* - 200: OK
*
* @param openApiRequest
* @return [Call]<[OpenApiResponse]>
*/
@PUT("api/open/")
fun createBook(@Body openApiRequest: OpenApiRequest): Call<OpenApiResponse>
/**
*
*
* Responses:
* - 200: OK
*
* @param id
* @return [Call]<[OpenApiResponse]>
*/
@GET("api/open/{id}")
fun findById(@Path("id") id: kotlin.Long): Call<OpenApiResponse>
/**
*
*
* Responses:
* - 200: OK
*
* @return [Call]<[kotlin.collections.List<OpenApiResponse>]>
*/
@GET("api/open/")
fun findResponses(): Call<kotlin.collections.List<OpenApiResponse>>
}
/**
*
*
* @param string
* @param number
* @param number2
* @param number3
* @param time
*/
data class OpenApiRequest (
@Json(name = "string")
val string: kotlin.String? = null,
@Json(name = "number")
val number: kotlin.Int? = null,
@Json(name = "number2")
val number2: kotlin.Long? = null,
@Json(name = "number3")
val number3: kotlin.Double? = null,
@Json(name = "time")
val time: java.time.OffsetDateTime? = null
)
/**
*
*
* @param string
* @param number
* @param number2
* @param number3
* @param time
*/
data class OpenApiResponse (
@Json(name = "string")
val string: kotlin.String? = null,
@Json(name = "number")
val number: kotlin.Int? = null,
@Json(name = "number2")
val number2: kotlin.Long? = null,
@Json(name = "number3")
val number3: kotlin.Double? = null,
@Json(name = "time")
val time: java.time.OffsetDateTime? = null
)
TEST
그러면 실제로 Server 코드와 Generate 된 Client 코드가 유기적으로 잘 통신하는지 확인하기 위해서 테스트를 해보도록 하겠습니다. Spring Boot 환경에서 사용할 것이기 때문에 아래처럼 retrofit2 사용을 위해서 @Bean을 하나 만들어줍니다. ApiClient
는 코드 제너레이트 될 때 함께 생성되는 OkHttpClient
입니다. 이는 본인이 직접 구성한 것을 사용하셔도 크게 상관 없습니다.
@Bean
fun openApiClientApi(): OpenApiControllerApi {
return ApiClient("http://localhost:8081")
.createService(OpenApiControllerApi::class.java)
}
그리고 Service 코드를 만든 후 호출하여 테스트 해보았습니다. 정상적으로 로깅되고 잘 응답값을 가져오는 것을 확인하였습니다.
@Service
class OpenApiClientService(
private val openApiControllerApi: OpenApiControllerApi
) {
fun aa() {
val findResponses = openApiControllerApi.findResponses().execute()
println("findResponses = ${findResponses.body()}") // findResponses = [OpenApiResponse(string=string, number=1, number2=2, number3=3.3, time=2022-09-15T12:23:29.801149Z), OpenApiResponse(string=string, number=2, number2=4, number3=6.6, time=2022-09-15T12:23:29.801160Z)]
}
}
마무리
오늘까지의 내용을 토대로하면 우리는 서버 Controller 코드를 수동으로 작성하는 것 만으로도 Open API Specification 기반의 yaml 파일을 만들어 낼 수 있고, Client에서 Server를 호출할 수 있는 코드까지 자동 생성할 수 있다는 것을 보았습니다. 이를 잘 활용하면 API 스펙을 server와 client가 잘못 작성하는 일은 줄어들 것입니다.
감사합니다.
참조
'기타 > 기타' 카테고리의 다른 글
ChatGPT 알아보기 - 개요와 기본 사용법 (1) | 2023.05.05 |
---|---|
[기타] CloudEvents v1.0.2 번역 및 정리 - 이벤트 메시지 스키마 스펙 (0) | 2023.04.19 |
Open API Spefication 활용하기 - 3편 ( Spring Boot 에서 yaml 파일 자동으로 만들기 ) (0) | 2022.09.12 |
Open API Spefication 활용하기 - 2편 ( yaml 파일 분석하기 ) (1) | 2022.09.09 |
Open API Spefication 활용하기 - 1편 ( Overview ) (0) | 2022.09.02 |
댓글