본문 바로가기
Backend/Spring

[Spring] 멀티 모듈에 Spring Rest Docs 적용기

by chickenman 2025. 3. 3.

문제

이번에 포트폴리오를 준비하면서 멀티 모듈로 구성된 어플리케이션을 만들어 보고 있다. 빌드 도구로는 gradle을 사용했다. (8.10.x)

 

모듈은 아래와 같이 구성되었었다.

1. Common 모듈: 테스트, 유틸 등의 공통 기능을 담고 있는 모듈

2. App 모듈: 어플리케이션 구동을 담당하는 모듈

3. User 모듈: 사용자 관련 기능을 담당하는 모듈

4. Sales 모듈: 매출 / 매입 등 재무 기능을 담당하는 모듈

 

각 모듈은 Controller, Service, Repository, Entity 등 모든 계층을 담고 있었고, 각 모듈별로 유닛 테스트부터 Spring Rest Document를 적용하기 위한 통합 테스트 코드를 가지고 있었다. 

 

그리고 각 App 모듈로 어플리케이션을 기동하기 때문에 App 모듈을 제외한 나머지 모듈은 모두 plain jar(실행할 수 없는 jar)로 패키징될 생각이었다.

 

모든 모듈은 app 모듈에서 implement 함으로써 실행되는 아키텍처로 구상했었다.

 

하지만..

 

이 구조로는 Rest document를 모듈의 Jar에 넣어 다른 Jar에 export 해주기 사실상 어려웠다. (가능해도 굉장히 복잡하고 이해가 어려울 것으로 예상된다.)

 

Spring Rest Docs 기능은 테스트를 통과한 기능에 대해서만 문서를 만들어준다는 점에서 문서와 시스템 안정성이라는 두마리 토끼를 잡아주는 문서화 기능이었다. (물론 테스트 코드가 완벽해야 한다.)

 

그렇기 때문에 빌드 순서는 항상 test -> asciidoc이 되어야 한다.

 

공식문서에서도 아래와 같이 bootJar task에서 만들어진 Api 문서(html)를 jar에 넣어서 완성하도록 가이드 되어있다.

 

하지만 내가 구상한 프로젝트 아키텍처에서 User, Sales 모듈은 실행 가능한 jar가 아닌 jar로 구성되어야 한다.

 

그리고 공식 문서에는 멀티 모듈 & 생성된 Api 문서(html)에 이미 만들어진 jar를 합칠 수 있는 방법은 가이드 되어 있지 않다.

 

시도

나는 이 문제 상황을 개선하기 위해 아래와 같은 시도를 해보았다. (거진 한 달 동안 시도했었어서 다 기억은 나지 않아요..ㅠ)

 

1. ./gradlew clean test jar / --rerun jar와 같은 방법을 사용해서 테스트가 완료된 후 jar를 리빌드하도록 설정

 

싱글 모듈에서는 "test" task에서 jar를 빌드하지 않는다.

 

아래는 spring rest docs 기능을 사용하는 모의 프로젝트를 만들어보고 "./gradlew test -i --dry-run" 명령어를 실행하여 test task를 실행하면 내부적으로 어떤 task가 동작하는지 확인해보았다. 그리고 그 결과는 아래와 같이 jar를 만들지 않는다.

 

멀티 모듈에서도 다른 모듈에 의해 의존되지 않는 모듈은 "test" task 실행시 "jar" task를 실행하지 않는다.

 

아래의 사진은 멀티 모듈에서 common 모듈을 어떤 모듈에서도 의존하지 않는 상황에서 "./gradlew test -i --dry-run" 명령어를 실행한 결과물이다.

(

app 모듈은 앱 구동을 위해 implementation으로 user 모듈을,

user 모듈은 App 모듈이 가지고 있는 @SpringBootApplication을 테스트에 사용하기 위해 testImplementation으로 app 모듈을 사용

)

다른 모듈에 의존되고 있는 app과 user 모듈은 테스트 전 "jar" task가 실행되었지만 common 모듈은 싱글 모듈 프로젝트에서처럼 "test" task만 실행되었다.

우리가 이 현상에서 중요하게 집고 넘어가야할 포인트는 다른 모듈에게 의존되기 시작한 순간 jar 파일이 만들어진다는 점이다.

(모든 built-task에 대해서는 아니다. build.gradle의 dependencies 항목에 의존성 환경(Ex. implementation, compileOnly)을 어떻게 설정했느냐에 따라서 달라진다.)

 

따라서, "test" task를 실행해도 이미 jar가 만들어저버리기 때문에 다시 jar가 빌드되지 않는다. (UP_TO_DATE이기 때문)

 

그럼 "--rerun jar"를 사용하는 것은 어떨까?

 

테스트해본 결과, rerun 옵션을 주어도 결국 "jar" task는 다시 실행되지 않았다. 실패의 이유는 멀티 모듈환경에서의 시스템 안정을 위한 gradle의 장치이지 않을까 생각이 든다.

 

"test" task 실행 시 app, user 모듈은 jar로 말려서 다른 모듈의 테스트에 사용되었을텐데, 이후에 다시 jar로 말아버리면 이전에 했던 task들의 의미가 없어져서 그런 것이 아닐까?

 

결론적으로 이 방법은 실패했다.

 

2. gradle로 테스트 완료 후 만들어진 api 문서를 포함한 새로운 jar를 만든다.

 

1번 해결책이 불가능하다면 다른 jar 파일을 만들면 되지 않을까? 라는 생각에 나온 해결책이 2번이다.

test -> asciidoctor를 거쳐서 Api 문서(html)를 만든 후 새로운 configuration으로 jar 파일을 만들어보았다. (Jar 타입의 custom task 생성)

 

하지만 jar 파일을 만들기 위해서는 이전에 compileJava, processResources, classes와 같은 task를 거쳐야 한다.

 

이전에 실행되었던 task를 통해 나온 산출물은 그대로이기 때문에 새로운 jar를 생성하는 task를 실행해도 동일한 jar가 나올 뿐이었다.

 

결론

물론 만들어진 jar 파일을 unzip하여 생성된 Api 문서(html)을 추가하고 다시 말아서 사용하도록 코드를 작성하는 것은 가능하다.

 

하지만 이 방식은 gradle에서 제공하는 task 및 incremental build의 관점에서 올바르지 못할 것 같다는 생각이 들었다. 

 

그래서 결론은.. 결국 구조를 바꾸기로 했다. (언제 다 해 ㅠ)

 

이제 API와 관련된 것들은 모두 App 모듈에 넣고, 서비스 로직은 User, Sales 모듈, 모듈에서 공통적으로 쓰이는 것들은 common 모듈에 넣기로 했다.

 

이번 트러블 슈팅 경험을 통해서도 다시 한 번 느낀 것 같다.

 

잘 안될때는 공식 문서를 읽자!

 

 

 

 

출처

https://docs.spring.io/spring-restdocs/docs/current/reference/htmlsingle/#getting-started-build-configuration-packaging-the-documentation

https://docs.gradle.org/current/userguide/build_lifecycle.html

https://docs.gradle.org/current/userguide/intro_multi_project_builds.html