데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      Ktor프레임웍 #2 : OpenAPI 활용

      LEN 24.10.23
      1,388 7 1
      DEVOTEE 요약
      이번 블로그에서는 REST API 서버 개발 시 OpenAPI Specification(OAS) 표준을 사용해 API 및 데이터 모델 명세를 작성하고, OpenAPI Code Generator로 서버 코드를 생성하는 방법을 설명합니다. 예제로는 학생(Student) 객체 모델과 이에 대한 CRUD API를 이용하며, 코틀린 서버 코드를 생성하려면 Gradle 빌드 파일에 openapi-generator 플러그인을 추가하고, 특정 디렉토리 설정을 통해 코드 생성 옵션을 설정합니다. 또한, Ktor 프레임워크를 이용해 생성된 모델들로 CRUD API 라우트와 관련된 샘플 코드를 작성하여 실행 방법을 소개합니다.
      DEVOTEE 추천 블로그

      이번 글에서는 REST API서버 개발시 서버가 제공하는 API들의 명세 작성(API,DataModel)를 OAS 표준으로 작성하고,

      OpenAPI Code Generator를 이용해서, 작성된 API스펙에 맞는 서버 코드를 생성하는 방법에 대해서 알아 봅시다.


      제약 사항

      • openapi-generator를 이용해서 API stub코드 까지 생성하지 않고 모델관련 코드들만 생성하고 Route를 작성하는 방식으로 알아 봅니다

        (아직 ktor의 type-safe Route작성용 코드 생성이 원활하지 않아, 조금더 버전업이 될때 까지 기다려야 될듯 합니다)


      서버가 제공할 API OAS스펙 작성하기

      이번 글에서 예시로 사용할 간단한 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 추가

      코드 생성시 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 executed


      생성된 파일 살펴 보기

      src/generated/kotlin폴더 및에 openapi 코드 생성기가 생성한 소스 파일들을 확인 할수 있습니다.

      image.png


      생성된 모델 파일들 살펴 보기

      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관련 코드들만 생성해서 예제를 작성했습니다. 이점 양해 부탁 드립니다.


      OpenAPI UI화면

      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화면을 확인 하실 수 있습니다.


      image.png

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      LEN 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기