Situation
이 프로젝트를 시행하게 된 첫 번째 배경은 바로 API 문서가 전혀 존재하지 않는다는 점에서였습니다.
Django 시절부터 존재하지 않았었고, 프런트가 동작하고 있었지만 어떤 걸 스스로 필요로 하는지 알지 못하여 이번에라도 만들어야겠다는 생각을 하였습니다.
특히 NestJS는 Swagger플러그인을 통해서 자동으로 문서화가 가능하다는 것으로 알고 있었지만, 이는 컨트롤러의 응답 객체를 클래스로 정의를 했을 때 가능한 것이었고, 저희와 같이 인터페이스로 정의되어있는 상태로는 불가능하였습니다.
기존에 인터페이스로 구현이 되어있는 것을 모두 클래스로 바꾸는 것도 방법이지만, 굳이 이렇게 돌아가는 방법을 택한 이유는 응답 객체는 인터페이스로 작성되는 것이 좋다고 생각하기 때문입니다. 왜냐하면 응답 객체는 서버에서 만들어내는 응답의 결과물이며, 다른 역할/책임이 존재하지 않기 때문입니다. 하지만 클래스는 하나의 주체로써 메서드를 가질 수 있습니다. 따라서, 객체지향적 패턴 관점으로 응답 객체가 클래스가 되는 것은 적절치 않다고 생각합니다.
이에 저는 AST를 이용해서 API 문서처럼 사용할 수 있는 특정한 클래스들을 만들기로 했습니다.
Task
해당 작업을 AST로 해야겠다고 생각한 뒤, 간단한 셋업을 만들어서 다른 팀원께 이관하였습니다. npm으로 배포하느냐, 아니면 github 서브 모듈로 배포하느냐에 대한 의견 차이가 있었지만 npm이 좀 더 표준적인 방식이기도 하고, submodule 업데이트나 설치가 생각보다 어렵습니다. 이전에 개인 작업할 때, 실수로 한 번 써봤는데, 커밋 단위로 맞춰야하기 때문에 특정 버전을 직관적으로 찾기가 쉽지 않았을 뿐더러, 컨플릭트도 잦아서 쉽지 않았습니다. 반면 npm의 경우 node_modules 제거 후 재설치만 해주면 된다는 점, 버전이 명시가 된다는 점 때문에 npm으로 선택하였습니다.
이후 해당 팀원분께서 잘 작업을 해주셨고 동아리를 떠나게 되었습니다. 해당 작업을 이어받아 찬찬히 살피던 중에는 몇 가지 빠진 점들이 있어 보강을 하였습니다. 예를 들어 ‘인증 필요 여부’ 나 ‘path param’ 같은 것들이 빠져 있어서 추가적인 작업 후에 다음과 같이 머지 후 배포 하였습니다.
하지만 실제로 사용하기 위해서 프런트엔드 단에서 크리티컬한 피드백이 몇 가지 들어왔습니다.
우선 타입이 있는 것은 좋지만 사용하기가 힘들다는 것이었습니다. 함수형 프로그래밍으로 요청 객체나 응답 객체를 검사하는데, class-validator는 그렇지 못하다는게 주요한 이유였습니다.
뿐만 아니라 response: ResponseType 으로 작성하다보니, ResponseType를 한 번에 보기가 어렵다는 것도 단점으로 작용하였습니다. 특히 Django 코드를 옮기면서 ResponseType 간에 얽혀있는 경우가 조금 있었는데 이것 때문에 보기가 힘들다는 것이었습니다.
이 피드백을 들은 후, 며칠 즈음 지나서 갑자기 문득 생각이 났습니다.
‘타입이 아니라 Swagger 문서를 만들어주는게 어떨까?’
swagger 또한 swagger.json이라는 문서로 만들어지게 됩니다. 따라서 AST를 이용해서 아예 json 문서를 만들게 되면 API 테스트부터 문서까지 모두 자동으로 만들 수 있었기에 기존 접근보다 훨씬 큰 임팩트를 기대할 수 있었습니다.
Action
Swagger에는 schema라는 영역이 있습니다. Swagger에서 사용되는 Schema를 모아놓는 곳인데요. 기존에 타입을 만들어줄 때에는, Interface로 작성된 타입을 그저 참조해서 명시해주기만 하면 되지만 이제는 그 안에 있는 property를 돌면서 하나씩 매핑해주어야했고, 그 작업이 재귀적으로 일어나야했습니다.
이에 이런 역할을 하는 라이브러리가 없는지 일단 찾아보았습니다.
찾아보니 interface를 swagger-json으로 만들어주는 라이브러리가 존재했습니다.
뿐만 아니라 request class로 들어오는 validator 또한 swagger 문서에 매핑되는 constraint로 매핑이 되어야했기에 class-validator를 json-schema로 만들어주는 방법 또한 찾아보았습니다.
정말 다행히, 이미 관련 라이브러리가 존재하던 참이었습니다.
이에 이 2개를 적극적으로 활용해보고자 했습니다.
우선 LLM을 이용하여 대강의 작업 개요를 알려주어서 뼈대를 잡은 다음, ts-morph 도큐먼트를 읽으면서 하나씩 수정해나가고자 했습니다.
기존의 방식대로 controller를 모두 읽어들여, @Controller 내 path param, query param, request, response 등을 분석하는 것이 주요한 작업이었습니다.
중간 중간 위 두 개가 사용하는 openapi 문서 버전이 맞지 않아 문법이 다른 이슈가 있기도 하였지만, 이는 직접 그 도큐먼트를 비교해가면서 수동으로 맞춰주었습니다. 만들어놓은 schema를 ref 하는 부분이었는데, 크리티컬 하지 않아 쉽게 고칠 수 있었습니다.
최종 코드는 다음과 같습니다.
코드
저희는 이것을 실행하는 로직을 postbuild에 포함시켜놓았고, 최종적으로 배포를 할 때마다 자동으로 생성되도록 하였습니다.
yarn generate:server:api-docs 가 바로 위 generator를 실행시키는 코드입니다.
Result
아래는 저희 swagger 문서 주소입니다.
아마 vpn으로 막혀있어 밖에서는 접속이 어려울 것 같습니다.
사진으로나마 간단히 보여드리자면
아래와 같이 API 테스트 또한 가능합니다.
이렇게 swagger 문서를 만들어주는 코드를 통해 다양한 변화가 일어났습니다.
먼저 프런트엔드에서 개발하기가 편해졌습니다.
특히 테스트도 함께 할 수 있어서, 현재 프런트엔드를 아예 새로 만들고 있는 작업에 속도가 날 것으로 보입니다.
백엔드에서도 API 개발 후 테스트가 간단해졌습니다. postman으로 하나씩 만들어서 테스트하는 것보다 훨씬 속도가 빠릅니다. 특히 intensive하게 작업하지는 않는 동아리의 구조 상, 문서에 대한 부분을 놓치기가 쉽습니다만, 일단 코드를 작성하면 이런 부분은 자동으로 해주니 편함과 동시에 신입이 들어왔을 때, 온보딩 기간이 조금 더 줄어들 것으로 생각합니다.
하지만 단점과 아쉬운 점도 분명히 존재하는데요.
단점으로는 배포 시간이 조금 더 늘어났습니다. 기존에 multi-stage 빌드를 하기에 원래도 조금 긴 편이었는데 이를 통해 조금 더 늘어났습니다.
아쉬운 점으로는 해당 코드가 다른 두 라이브러리에 의존한다는 점입니다. 아예 처음부터 새로 만들었으면 훨씬 좋았을 것이라 생각합니다. 뿐만 아니라, 사실 원래는 순서가 거꾸로 되는 것이 맞다는 점입니다. swagger-schema와 문서를 잘 정의해놓고 이를 기반으로 type을 generate하는 것이 맞는 방향입니다. 왜냐하면 schema는 서버와 클라이언트 간의 약속이기 때문에, 정상적인 프로세스라면 이 약속이 합의되면서 api 스키마가 완성이 되고 이를 기반으로 같은 타입을 generate하여 클라이언트와 서버가 동시에 사용하는 것이 가장 이상적이라 생각합니다.
특히 swagger 문서는 언어와 상관없기에, 이 generator를 활용하면 각자의 언어에 맞게 바꿀 수 있다는 장점도 존재합니다.
java의 경우에는 아래와 같은 라이브러리가 존재하고 이는 Typescript 또한 지원해줍니다.
Typescript에서 사용되는 다른 라이브러리의 경우에는 아래와 같은 라이브러리도 있습니다.
하지만 저희 같이 모든 사람이 사실상 ‘비대면’, ‘파트 파트타임’으로 근무하는 조직의 경우에는 이런 협의를 하기가 굉장히 힘듭니다. 각자 만들기 바쁘기 때문이죠. 그렇기에 서버에서 일단 구현한 후 swagger 문서를 생성하는 것이 훨씬 빠른 것 같습니다.
OTL 마이그레이션 이후로는 전체 업무 플로우에서 개선 효과가 좋았던 작업인 것 같습니다. 또한 개인적으롤 Java Reflection을 처음 알게 된 순간부터 MapStruct 구현을 뜯어보면서 AST를 이용한 작업의 포텐셜이 크다고 생각하였는데, 이번 작업을 통해 AST를 활용한 문서 생성기라는 큰 컨트리뷰션을 할 수 있어서 뿌듯했습니다.