관리 메뉴

취미개발 블로그와 마음수양

코드실험실(?) - RestDocs 필드 입력 자동..? 본문

개발관련 잡다/개발노트-글

코드실험실(?) - RestDocs 필드 입력 자동..?

아라한사 2020. 8. 9. 15:02

한줄요약 : 토이느낌 프로그램 하나 만들면서 RestDocs 썼는데 

일일이 요청/응답 필드 문서화 적는거 귀찮아서
요청응답 DTO 클래스 리플렉션 처리해서 문서화시켜주는거로 처리했는데 gist 코드도 같이 올렸다..
사용한 라이브러리버전도 0.1 이고 그냥 토이레벨일듯한..?

서론

코드 가지고 이야기 나눌 사람이 많지는 않다보니.. 

(이건 어떨 것같고 저건 어떨 것같고 뭐 그래요.. 뭐 이런..) 

 

가끔 이런 글을 적어서 주변 분들에게 이렇습니다. 요래요래 해봤습니다 하면 피드백이 별로 없어서.. ( =ㅅ=)a 
(딱히 피드백을 바라는 것은 아니고.. 그냥 오 그렇군요요 정도? ㅋㅋ 바랬던..음.. )

흠 그렇지만 뭐 적어봅니다.

 

=====

 

요즘 자원봉사의 느낌으로 실무콘 홈피를 조금씩 만들어보고 있긴한데..

PPT로 다시 적을지도 모르겠지만 역시 느낀점은 

 

개발하기 전 첫 생각은 무척 간단하다..

"어 그냥 이거 소셜 로그인 처리시킨 다음에,

신청한 행사 중에 현재 진행중인 행사 있으면 zoom 주소 보여주면 되는거 아닝가?" 

"간단히 만들겠는걸..?"

 

(음 .. 역시 하지만 막상 해보면 간단하면서도 또 고민해볼 문제를... 주었던 )

개발하면서 생각했던 이런저런 상태처리는 논외로 하고, RestDocs 문서화하면서 생각했던 부분을 적어보자면..

 

협업의 고민

( 작명을 초기에 졸면서 적어서..크흠 )

 

이번엔 프론트엔드쪽은 다른 분께서 같이 봉사(?)에 협업으로 참여해주시기로 얘기가 되어서.. 

테스트코드나 스프링 코드 봐주시면서 작업해주시는게 베스트..라고 말은 했지만, 

스프링 코드를 별로 봐오시지 않은 분께

테스트 돌려봐주시고 나오는 요청응답을 봐주세요 라고 말하기엔 

역시 현실적으로 무리가 있다 싶어 결국 rest docs 처리로 문서화를 드리기로 했는데.. 

mockMvc.perform(RestDocumentationRequestBuilders.get("/유저정보")
                .contentType(MediaType.APPLICATION_JSON)
                .header("Authorization", token))
                .andDo(print())
                .andExpect(status().isOk())
                .andDo(document("user-info", responseFields(
                        fieldWithPath("name").type(JsonFieldType.STRING).description("유저네임"),
                        fieldWithPath("email").type(JsonFieldType.STRING).description("유저이메일"),
                        fieldWithPath("modificationTime").type(JsonFieldType.STRING).description("최근 수정시간"),

 

이런 유저정보를 호출하는 문서화를 하나 적었을 때는 괜찮았는데.. 

두개 세개쯤 적으면서 요청응답 DTO 클래스 필드들을 일일이 적다보니..

머리가 뜨거워지기 시작하였다.

(요청/응답 클래스가 변하게 되면.. 여기 테스트 문서화 부분도 변경을 또 해줘야하는 거싱가..?! 뚜뚱)

(아니 밸리데이션 조건들도 문서에 다 적어줘야하잖아..?!)

(아니 요청응답 필드들이 많아지면 나는 저걸 다 적어야 하는 것인가 두둥..
이건 지인들끼리 노는 정도의 프로그램인데.. )

 

수정 : 그러니까..  윗 방식을 두세번 치다보니 느껴진 점

1) 문자열 변수 설명들을 그대로 타이핑 하려니 너무 힘들다. 변수가 10~20개되는 객체면..어떻게..?!

2) DTO쪽 변경이 일어나면 다시 또 테스트부분들도 수정해야 할 것같았다.

3) 테스트 코드가 자꾸 길어지는 것 같았다.

4) 밸리데이션 조건들도 원래 API 사용자에게 상세히 알려줘야하는데 그걸 일일이 다 타이핑하려니 힘들 것같았다.

5) DTO 쪽에도 주석을 달텐데.. 내가 개발한 백엔드 코드를 난 나중에 시간이 엄청지나서 DTO 를 보는데 설명이 DTO/ 테스트 양쪽에 있으면 설명도 중복인 게 좀 힘들것같았다. 
=> 이런 생각으로 머리가 뜨거워졌다.

 

( 코로나는 아니고, 기분탓,날씨탓이겠지..)

없는 짱구 굴리기

 

그래서 생각났던 방향이 스프링 핸들러매핑에서 생각이 났는데

스프링 deprecated된 예전 컨트롤러 방식에  CommonsPathMapHandlerMapping 라는 것이 있었는데
그 Mapping 방식이 javadoc 에 PathMap 어쩌고 설정된 부분을 매핑으로 읽는다길래.. 

 

 

CommonsPathMapHandlerMapping  예시

 

오오 javaDoc 을 컴파일시점에 읽는건가..? 싶어서 이거 비슷한 기능이나 찾아볼까 .. 

하니 다음과 같은 깃헙이 나왔다.. 

https://github.com/dnault/therapi-runtime-javadoc

 

dnault/therapi-runtime-javadoc

Read Javadoc comments at run time. Contribute to dnault/therapi-runtime-javadoc development by creating an account on GitHub.

github.com

클래스의 자바독을 읽어와준다니.. 뭐 그러면 요청/응답 클래스에 javaDoc 적어주고, 문서화는 여기위주로 가게하고

하면 되겠군.. 하면서 어쨌든 만들어주게 된 코드는 다음과 같이 되버렸다. 

 

mockMvc.perform(RestDocumentationRequestBuilders.post("/다른요청응답주소")
                .contentType(MediaType.APPLICATION_JSON)
                .content(req)
                .header("Authorization", token))
                .andDo(print())
                .andDo(document("다른요청문서"
                        , transFieldDiscriptorHolder(어쩌고Create.class).getReq()
                        , transFieldDiscriptorHolder(어쩌고Res.class).getRes()))

 

 

뭐 이렇게 적어주면 이런 자바독에 맞춰서 이런 문서화가 생기더라.. 

이런 자바독

/**
* 닉네임
*/
@NotBlank
@Length(min = 1, max = 10)
private String nick;

/**
* 예금자 성명
*/
@NotBlank
@Length(min = 1, max = 10)
private String depositor;

/**
* 신청자 이메일
*/
@NotBlank
@Length(min = 3, max = 50)
private String email;

이런 문서화

 

구현의 설명?

 

나름의 생각했던 부분은 Rest docs 응답에 description 을 적을 부분을, 

클래스 리플렉션을 통해서 읽어서 자동으로 좀 채워줘야한다는 부분인데...

이 설명의 내용이...

 

1. 자바독 설명 (자바독 설명이 없어도 문자열을 추가한다.)
2. 어노테이션 NotBlank, NotNull 이 있을 시 (필수)로 입력, 없을 시는 (선택) 으로 되게
3. 숫자타입 Long, int, Double, Float 인 경우 숫자로 처리
4. Length 가 있을 경우 추가 글자 처리. 완료된 문자열은 다음과 같다.

==> [숫자][문자열]자바설명 (필수) (3~50글자)

 

여기서 어노테이션 관련, 타입관련, Length 관련을  설명자 클래스 하나하나로 둬서 한줄의 정보를 완성시킨 뒤에

필드별로 RestDocs의 FieldDescriptor 로 리턴되게 하면 윗 코드처럼 그냥 요청/응답 클래스 정보를 읽게 해서 처리시켰다.. 

 

그냥 급하게 적은 거긴한데.. 코드를 소개하자면 다음과 같다..

(테스트 유틸 메서드 하나 만들면서 그냥 주욱~ 써내려간거라;; 뭐 그렇다 크흡 ) 

public static FieldDiscriptorHolder transFieldDiscriptorHolder(Class clazz) {
        Map<String, String> docuMap = new HashMap<>();
        ClassJavadoc classDoc = RuntimeJavadoc.getJavadoc(clazz.getName());
        // 자바독 좀 추가해놓고
        for (FieldJavadoc fieldJavadoc : classDoc.getFields()) {
            docuMap.put(fieldJavadoc.getName(), fieldJavadoc.getComment().toString());
        }
        
        List<FieldDescriptor> fieldDescriptors = new ArrayList<>();
        
		// 클래스 필드들 읽어와서
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String name = field.getName();
			
            // 필수 인지 아닌지
            boolean isRequired = false;
            
            // 길이 관련 어노테이션이 있는지
            LengthDescriptor lengthDescriptor = null;
            
            // 타입 설명처리
            TypeDescriptor typeDescriptor = new TypeDescriptor(field.getType());

            for (Annotation anno : field.getAnnotations()) {
                if (anno.annotationType().isAssignableFrom(NotNull.class) ||
                    anno.annotationType().isAssignableFrom(NotBlank.class)) {
                    isRequired = true;
                }
                if (anno.annotationType().isAssignableFrom(Length.class)) {
                    lengthDescriptor = new LengthDescriptor((Length) anno);
                }
            }

            String comment = docuMap.get(name);
            comment = comment == null ? "" : comment;
            
            // 실무콘 필드 설명자 만들어서 리스트에 추가
            SilmuconDescriptor silmuconFieldDescriptor = new SilmuconDescriptor(name, isRequired, typeDescriptor, lengthDescriptor, comment);
            fieldDescriptors.add(silmuconFieldDescriptor.toRestDocsFields());
        }

        return new FieldDiscriptorHolder(fieldDescriptors);
    }

전체적인 코드는 그냥 gist 로 올려봅니다.  저야 뭐 그냥 약간 괜찮은 토이레벨의 실무콘 만들기라.. 

 

https://gist.github.com/arahansa/c62e3c750773d5dd75abd2051e326917

 

restdocs클래스리딩관련.java

GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

좀 더 고쳐서 적는다면 클래스에서 잘못되게 정보를 읽을 수도 있으니 오버라이드할 수 있게 한다던가,
클래스 안의 클래스등에 대해서 필드를 읽을 수도 있어야하겠는데 지금은 이정도만.. 뭐.. 

 

결론

뭐 .. 그냥 손가락 많이 치는 것은 할만하긴한데, 그럼에도 불구하고

이거 사람이 할일이 아닌 것같은데 하는 것을 자동화를 좋아하다보니 요상한 짱구를 굴렸다.

각자의 시간과 손가락은(?) 소중하니까...  

 

장점 :

요청클래스의 밸리데이션 조건 변경, 필드변경에 대하여 신경안쓰고 문서를 작성할 수 있다.

요청클래스같은 부분에 자바독만 걸어두면 문서작성이 손이 덜간다. (필드가 10개가 넘어가면 정신이 아득해질 것같았다. )

 

단점 : 

테스트시간이 조~금 길어지긴 하더라.. 테스트 한 개만 봤는데 100ms 정도였던 걸로 기억.

지인들과 노는 정도의 프로그램인데 뭐 .. 

DTO 안의 중첩클래스는 좀 거시기하겠지..

 

(같이 백엔드 좀 보강해준다고 했던 지인에게 감사의 인사와 허세를.. ㅋㅋ) 

( 금방이죠 라고 적었는데 이 금방의 시간은, 꾸역꾸역(?) 신경 쓰고 난 뒤의 체감시간이지
그렇다고 해서 모든 시간이 양이 적다거나 덜 소중하다는 아니겠지만.. 흠.. 뭐 아무튼 그럼 이만.. )

 

==> 

지인개발자에게 딱 요정도 피드백 받아보려고 글 써봅니다. ㅋㅋ