ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Kotlin에서 Rest Docs 문서화 코드 개선하기
    Backend/Spring 2023. 5. 29. 18:08

    기존에는 테스트 결과를 문서화 하기 위해, Rest Docs에서 제공하는 FieldDescriptor를 다음과 같이 생성하였다.

    그러다 보니 Field가 많으면 엄청난 양의 코드를 작성해야 하고 이 부분의 가독성이 떨어져 개선하고자 한다.

     

    ❌ 문제의 코드

    restDocMockMvc.post("/test") {
        jsonContent(request)
    }.andExpect {
        status { isOk() }
    }.andDo {
        handle(
            MockMvcRestDocumentation.document(
                "{class-name}/test-success",
                requestFields(
                    fieldWithPath("name").description("이름").attributes(field(EXAMPLE, request.name))
                ),
                responseFields(
                    fieldWithPath("hashValue").description("name의 hashCode 값"),
                    fieldWithPath("originalName").description("trim을 적용한 name 값")
                )
            )
        )
    }

    🤦‍♂️ 해당 코드는 반복적이고, 코드가 잘 읽히지 않는다.

     

    ✔ 개선 코드

    restDocMockMvc.post(targetUri) {
        jsonContent(request)
    }.andExpect {
        status { isOk() }
    }.andDo {
        createDocument(
            "test-success",
            requestBody("name" type STRING description "이름" example request.name),
            responseBody(
                "hashValue" type NUMBER description "name의 hashCode 값",
                "originalName" type STRING description "trim을 적용한 name 값"
            )
        )
    }

    방법

    Kotlin에서는 infix function확장 함수를 제공한다. 해당 기능을 사용해서 코드를 개선할 생각이다.

     

    💬 Infix Function

    두 개의 객체 중간에 들어가게 되는 함수를 의미한다.

     

    👀 예시

    val monday: Pair<String, String> = "Monday" to "월요일"

    ◼ 위와 같이 to라는 infix function을 사용해서 Pair 객체를 생성할 수 있다.

     

    💬 확장 함수

    기존에 정의된 클래스에 함수를 추가하는 기능이다.

     

    👀 예시

    fun MutableList<Int>.swap(a: Int, b: Int){
    	val tmp = this[a]
    	this[a] = this[b]
    	this[b] = tmp
    }

    ◼ 위와 같이 MutableList 클래스에 swap이라는 함수 기능을 추가할 수 있다.

     

    개선 과정

    FieldDscriptor를 가지고 있는 class를 만든다.

    class RestDocsField(
        val descriptor: FieldDescriptor
    ) {
        infix fun isOptional(value: Boolean): RestDocsField {
            if (value) descriptor.optional()
            return this
        }
    
        infix fun description(value: String): RestDocsField {
            descriptor.description(value)
            return this
        }
    
        infix fun example(value: String): RestDocsField {
            descriptor.attributes(field(EXAMPLE, value))
            return this
        }
    }

    ◼ RestDocsField 클래스에서 FieldDescriptor를 받는다.

    ◼ infix 함수를 통해 FieldDescriptor에 해당 값을 추가한다.

     

    String 타입에 확장 함수 추가

    infix fun String.type(
        type: JsonFieldType
    ): RestDocsField = createField(this, type)
    private fun createField(
        path: String,
        type: JsonFieldType
    ): RestDocsField = RestDocsField(PayloadDocumentation.fieldWithPath(path).type(type))

    ◼ String 클래스에 type이라는 infix 함수를 추가한다.

    ◼ JsonFieldType은 Rest Docs에서 제공하는 class로 필드의 type 지정할 수 있다.

     

     "code" type JsonFieldType.NUMBER

    ◼ 위와 같이 코드를 작성하면, type 이라는 infix 함수에 "code" 가 this로, JsonFieldType.NUMBER가 type 인자로 들어간다.

    ◼ createField 함수를 통해 path에 code를 type에는 JsonFieldType.NUMBER가 저장되어 FieldDescriptor 객체가 생성되고 해당 객체를 인자로 받는 RestDocsField 객체가 반환된다.

     

     "code" type JsonFieldType.NUMBER description "응답 코드"

    ◼ 위와 같이 코드를 작성하면, "code" type JsonFieldType.NUMBER 가 RestDocsField로 변환된다.

    ◼ 이후, 생성된 ResDocsField의 description infix 함수를 통해 FieldDescriptor 의 description 값으로 "응답 코드" 가 저장된다.

     

    ResponseFieldsSnippet 만들기

    fun responseBody(vararg fields: RestDocsField): ResponseFieldsSnippet =
    PayloadDocumentation.responseFields(fields.map { it.descriptor })

    ◼ 이제 생성된 RestDocsField를 인자로 받아 responseFields 함수에 RestDocsField의 descriptor를 인자로 넘겨주면 완성된다.

     

    💬 vararg 키워드

    가변 인자를 사용하면 함수를 호출할 때, 인자의 개수를 유동적으로 지정할 수 있다.

     

    MockMvcResultHandlersDsl에 확장 함수 추가

    fun MockMvcResultHandlersDsl.createDocument(identifier: String, vararg snippets: Snippet) {
        return handle(MockMvcRestDocumentation.document("{class-name}/$identifier", *snippets))
    }

    ◼ createDocument로 snippet을 저장할 폴더 이름과 만든 snippets을 인자로 받아 anDo 함수를 실행한다. 

     

    결론

    코틀린에서 제공하는 다양한 기능을 활용하여 기존 코드를 개선할 수 있었다. 반복적인 코드를 최소한으로 줄이고 가독성을 높여 작성하기 쉽고 이해하기 쉬운 코드를 작성할 수 있었다.

     

    'Backend > Spring' 카테고리의 다른 글

    Spring Event 사용하기  (0) 2023.06.29
    kotlin + HttpInterface 알아보기  (0) 2023.06.22
    Kotest를 통한 DCI 패턴 적용  (0) 2023.05.27
    spring에서 flyway 사용하기  (0) 2023.05.26
    [Spring] checkstyle 적용하기  (0) 2023.05.03

    댓글

Designed by Tistory.