23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
이번 글에서는 REST API서버 개발시 서버가 제공하는 API들의 명세 작성(API,DataModel)를 OAS 표준으로 작성하고,
OpenAPI Code Generator를 이용해서, 작성된 API스펙에 맞는 서버 코드를 생성하는 방법에 대해서 알아 봅시다.
제약 사항
openapi-generator를 이용해서 API stub코드 까지 생성하지 않고 모델관련 코드들만 생성하고 Route를 작성하는 방식으로 알아 봅니다
(아직 ktor의 type-safe Route작성용 코드 생성이 원활하지 않아, 조금더 버전업이 될때 까지 기다려야 될듯 합니다)
이번 글에서 예시로 사용할 간단한 Student객체 모델 및 CRUD API 4개에 대한 OAS스펙을 아래처럼 작성해서 src/main/resources/openapi/student-server.yaml 파일로 저장 합니다.
openapi: 3.0.3
info:
title: Application API
description: Application API
version: 1.0.0
servers:
- url: 'http://0.0.0.0:8080'
paths:
'/students/{id}':
get:
description: 검색
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Student'
tags:
- student
operationId: GetStudyentById
parameters:
- schema:
type: integer
name: id
in: path
required: true
description: 학생 번호
delete:
summary: ''
operationId: DeleteStudentById
responses:
'200':
description: OK
tags:
- student
description: 삭제
put:
summary: ''
operationId: ModifyStudentNameById
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Student'
description: 이름 변경
parameters:
- schema:
type: string
in: query
name: name
tags:
- student
/students:
post:
summary: ''
operationId: CreateStudent
responses:
'200':
description: OK
content: {}
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Student'
tags:
- student
description: 생성
components:
schemas:
Student:
title: Student
x-stoplight:
id: uzj24zwgu2i4g
type: object
properties:
id:
type: integer
x-stoplight:
id: j95f1rgqln8ph
format: int32
name:
type: string
x-stoplight:
id: hu1vz1rly9iew
age:
type: integer
x-stoplight:
id: k22k050uutk73
format: int32
grade:
$ref: '#/components/schemas/Grade'
address:
$ref: '#/components/schemas/Address'
Address:
title: Address
x-stoplight:
id: lo2s47ve0df22
type: object
properties:
city:
type: string
x-stoplight:
id: 3b7xc3dvudzxq
address1:
type: string
x-stoplight:
id: ioftwayp36zzi
address2:
type: string
x-stoplight:
id: fgjpoo2tlr2hz
Grade:
title: Grade
x-stoplight:
id: ph4wgcwhejv1q
type: string
enum:
- GRADE_A
- GRADE_B
- GRADE_C(YAML파일 중간 중간에 x-stoplight.id값들이 나오는데, 이 필드들은 OAS표준 스펙 사항이 아닙니다. 궁금하시더라도 무시해 주시면 됩니다.)
앞에서 작성한 스펙 파일을 이용해서 프로젝트에서 코드를 생성 하려면 다음과 같은 프로젝트 빌드 설정 추가가 필요 합니다.
openapi generator gradle plugin 추가 설정
생성된 코드에서 사용하는 패키지 의존성 추가 설정
생성된 코드들 디렉토리를 코드 셋에 추가
$(projectRoot)/src/generated/kotlin디렉토리
코드 생성 옵션 설정
kotlin-server생성기의 ktor library로 코드 생성.
build.gradle.kts파일에 위의 설정 사항들을 아래처럼 추가 합니다.
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ktor)
// openapi: #1. openapi generator gradle plugin 추가 설정
id("org.openapi.generator") version "7.8.0"
}
group = "example.com"
version = "0.0.1"
application {
mainClass.set("io.ktor.server.netty.EngineMain")
val isDevelopment: Boolean = project.ext.has("development")
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
}
repositories {
mavenCentral()
}
dependencies {
implementation(libs.ktor.server.content.negotiation)
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.resources)
implementation(libs.ktor.serialization.gson)
implementation(libs.ktor.server.call.logging)
implementation(libs.ktor.server.call.id)
implementation(libs.ktor.server.openapi)
implementation(libs.ktor.server.netty)
implementation(libs.logback.classic)
implementation(libs.ktor.server.config.yaml)
// --> logstash appender
implementation("net.logstash.logback:logstash-logback-encoder:6.3")
// openapi: #2. 생성된 코드에서 사용하는 패키지 의존성 추가 설정
implementation("org.openapitools:openapi-generator:7.1.0")
implementation("com.squareup.moshi:moshi-kotlin:1.14.0")
implementation("com.squareup.okhttp3:okhttp:4.10.0")
testImplementation(libs.ktor.server.test.host)
testImplementation(libs.kotlin.test.junit)
}
// openapi: #3. 생성된 코드들 디렉토리를 코드 셋에 추가.
kotlin {
sourceSets["main"].apply {
kotlin.srcDir("src/generated/kotlin")
}
}
//openapi: #4. 코드 생성 옵션 설정
openApiGenerate {
generatorName.set("kotlin-server")
inputSpec.set("$projectDir/src/main/resources/openapi/student-server.yaml") // API 스펙 파일 경로
outputDir.set(project.file("./").absolutePath)
apiPackage.set("example.com.provide.apis")
modelPackage.set("example.com.provide.models")
globalProperties.set(
mapOf(
"apis" to "_NO_CODE_GEN_", // API관련 코드는 생성하지 않는다.
"models" to "",
"verbose" to "true",
)
)
configOptions.set(
mapOf(
"library" to "ktor",
"sourceFolder" to "src/generated/kotlin",
"packageName" to "example.com.provide"
)
)
additionalProperties.set(
mapOf()
)
}코드 생성시 SKIP할 파일들의 rule를 설정하지 않으면, 불필요한 파일이나, 기존 프로젝트의 파일이 over-write되는 불상사가 발생 합니다.
꼭 아래 처럼 Rule을 추가해 주시고, 이 부분은 드문 경우이지만, generator버전이 바뛸 경우 변경이 필요할 수 있습니다.
$(projectRoot)/.openapi-generator-ignore 파일을 아래 처럼 작성 합니다.
src/generated/**/provide/apis/**Api.kt
src/generated/**/provide/infrastructure/**
build.gradle
settings.gradle
README.md] ./gradle openApiGenerate
> Task :openApiGenerate
################################################################################
# Thanks for using OpenAPI Generator. #
# Please consider donation to help us maintain this project 🙏 #
# https://opencollective.com/openapi_generator/donate #
# #
# This generator's contributed by Jim Schubert (https://github.com/jimschubert)#
# Please support his work directly via https://patreon.com/jimschubert 🙏 #
################################################################################
Successfully generated code to /Users/mind/study/kotlin/example
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.4/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executedsrc/generated/kotlin폴더 및에 openapi 코드 생성기가 생성한 소스 파일들을 확인 할수 있습니다.
OAS스펙에 정의된 모델들은 src/generated/kotlin/example/com/provide/models폴더 및에 생성 되어 있습니다.
Student모델 정의 파일(워낙 간단한 모델인 관계로 설명은 생략 하겠습니다.)
/**
* Application API
* Application API
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
package example.com.provide.models
import example.com.provide.models.Address
import example.com.provide.models.Grade
/**
*
* @param age
* @param grade
* @param id
* @param name
* @param address
*/
data class Student(
val age: kotlin.Int,
val grade: Grade,
val id: kotlin.Int? = null,
val name: kotlin.String? = "_NO_INPUT",
val address: Address? = null
) Address.kt
/**
* Application API
* Application API
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
package example.com.provide.models
/**
*
* @param city
* @param address1
* @param address2
*/
data class Address(
val city: kotlin.String,
val address1: kotlin.String? = "도봉구",
val address2: kotlin.String? = null
) Grade.kt
/**
* Application API
* Application API
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
package example.com.provide.models
/**
*
* Values: A,B,C
*/
enum class Grade(val value: kotlin.String) {
A("GRADE_A"),
B("GRADE_B"),
C("GRADE_C");
}아래 처럼 생성된 모델 클래스들을 이용해서 CRUD 샘플 코드를 작성해 봅시다.
package example.com.domain.student
import example.com.provide.models.Address
import example.com.provide.models.Grade
import example.com.provide.models.Student
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Route.studentRoutes() {
val sample = Student(
id = 10,
name = "둘리",
age = 7,
grade = Grade.A,
address = Address(city = "서울", address1 = "도봉구", address2 = "쌍문")
)
post("/students") {
val className = call.parameters["className"]
val body = call.receive<Student>()
println("등록할 학생 정보 : $body")
call.respond(sample)
}
get("/students/{id}") {
val id = call.parameters["id"]
println("조회할 학생 ID: $id")
call.respond(sample)
}
delete("/students/{id}") {
val id = call.parameters["id"]
println("삭제할 학생 ID: $id")
call.respond(HttpStatusCode.OK)
}
put("/students/{id}") {
val id = call.parameters["id"]
val name = call.parameters["name"]
println("이름을 변경할 학생 ID: $id, 변경할 이름 : $name")
call.respond(sample.copy(name = name))
}
}추가한 학생 관련 라우트들을 Application설정에 추가 합니다.
src/main/kotlin/example/com/Applicatoin.kt파일
package example.com
import example.com.domain.people.employeeRoutes
import example.com.domain.student.studentRoutes
import example.com.plugins.*
import io.ktor.server.application.*
import io.ktor.server.routing.*
fun main(args: Array<String>) {
io.ktor.server.netty.EngineMain.main(args)
}
fun Application.module() {
configureAdministration()
configureSerialization()
configureMonitoring()
configureHTTP()
configureRouting()
routing {
/** employee관련 CRUD API endpoint 라우트 등록 */
employeeRoutes()
/** Student관련 CRUD API endpoint 라우트들 등록 */
studentRoutes()
}
}빌드 하고 실행해 보기
] rm -rf ./src/generated
] ./gradlew openApiGenerate
] ./gradlew clean build -x test
] java -jar ./build/libs/com.sample.ktor_example-all.jar sample CURL commands
curl -X POST 'http://localhost:8080/students' \
-H 'Content-Type: application/json' \
-d '{
"id": 1,
"name": "홍길동",
"age": 20,
"grade": "GRADE_A",
"address": {
"address1": "서울특별시 강남구 테헤란로 123",
"city": "서울",
"address2": "06234"
}
}'
curl -X GET 'http://localhost:8080/students/1' \
-H 'Content-Type: application/json'
curl -X PUT \
--url 'http://0.0.0.0:8080/students/1?name=Mr.KIM' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json'
curl -X DELETE 'http://localhost:8080/students/1' \
-H 'Content-Type: application/json'Ktor프레임워크는 서버 개발시 Resources플러그인과 kotlinx-serialization을 사용해서 타입 세이프하게 서버 API를 작성하는 방법을 지원한다.
하지만, 아직 openapi-generator를 이용해서 코드를 생성할 경우, 생성된 stub코드를 기반으로 사용자 코드를 작성하는게 원활하지 않아,
부득이 하게 API스펙에 정의된 Model관련 코드들만 생성해서 예제를 작성했습니다. 이점 양해 부탁 드립니다.
spring의 swagger-ui화면과 비슷하게 openapi UI화면을 통해서 서버가 제공하는 API스펙을 서빙 할수 있습니다.
아래 처럼 HTTP.kt파일에 코드를 추가해 봅시다.
package example.com.plugins
import io.ktor.server.application.*
import io.ktor.server.plugins.openapi.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureHTTP() {
routing {
// openAPI: swaggerFile경로를 예시로 만든 student-server.yaml파일로 추가해 준다.
openAPI(path = "openapi", swaggerFile = "openapi/student-server.yaml")
}
}소스 빌드 후 재기동 시키고, 브라우저에서 http://localhost:8080/open 로 화면을 오픈하면 아래와 같은 UI화면을 확인 하실 수 있습니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.