멀티모듈 프로젝트 만들기

DataGSM은 사실 MSA다..?
MSA는 기능을 하나의 서버 애플리케이션에 모으지 않고, 도메인 경계별로 서비스를 나눠 각각을 별도의 애플리케이션으로 띄운 뒤 하나의 클러스터로 묶는 아키텍처입니다. 교내 프로젝트에서는 굳이 이렇게까지 나눌 이유도 별로 없고 기술적으로도 부담이 커서, 한번 해보고 싶더라도 실제로 시도하는 경우는 드물었습니다. 그런데 DataGSM은 제한적이나마 MSA 형태로 구성되어 있습니다. 어쩌다 이런 구조가 되었는지 정리해 보려 합니다.
모놀리식에서 시작
DataGSM도 처음에는 평범한 Spring Boot 모놀리식 애플리케이션이었습니다. 이유는 간단했습니다. 익숙했고, 빠르게 개발해야 하는 상황이었기 때문입니다.
문제는 여기서 시작됐습니다. DataGSM의 Web 패널이 쓰는 API, OpenAPI, OAuth는 인증 방식이 전부 달랐습니다. '데이터를 제공한다'는 큰 틀은 같아도 제공하는 방식은 서로 완전히 독립적인 기능이었는데, 이걸 하나의 애플리케이션에 담다 보니 코드가 점점 얽히고설켰습니다.
사실 이 정도는 헥사고날 아키텍처나 모듈러 모놀리스 같은 방식으로도 충분히 풀 수 있는 문제였습니다. 그래도 저희는 멀티모듈을 택했습니다. 도메인 간의 독립성을 지키고, 애플리케이션 구조가 복잡해질 여지를 처음부터 어느 정도 막을 수 있다고 봤기 때문입니다.
팀 사정도 있었습니다. 더모먼트팀에는 매년 개발을 시작한 지 1년이 채 안 된 신규 팀원들이 들어오고, 이제 좀 개발을 해봤다 싶은 사람들은 졸업해 버립니다. 이런 환경에서는 내부 구조가 복잡한 하나의 코드베이스보다, 코드베이스 자체가 어느 정도 나뉘어 있는 편이 낫다고 판단했습니다.
인증 방식대로
그렇게 처음에는 애플리케이션을 세 개로 나눴습니다.
datagsm-web: 일반 JWT 인증datagsm-auth: 외부 서비스가 쓸 수 있도록 설계한, OAuth 2.1 표준을 따르는 인증 시스템datagsm-openapi: 암호화된 키 기반의 API 키 인증
나눈 기준은 인증 방식이었습니다. 여기에 기능마다 할당된 Rate Limit 한도도 서로 달랐기 때문에, 애플리케이션 자체를 분리해 이런 설정의 복잡도를 줄이는 것이 최선이라고 판단했습니다.
UserInfo 엔드포인트
그래도 문제가 남아 있었습니다. 이번에는 표준과 관련된 문제였습니다. OpenID Connect 스펙은 Access Token으로 사용자 정보를 조회할 수 있는 UserInfo 엔드포인트(/userinfo)를 정의하고 있습니다. 저희는 이 스펙을 따르면서, 토큰을 발급하는 인가 서버와 사용자 정보를 제공하는 리소스 서버의 역할을 분리하는 설계를 택했습니다. 그런데 기존의 세 애플리케이션 구성으로는 이 분리를 구현할 수 없었습니다.
결국 인증 쪽을 datagsm-authorization과 datagsm-userinfo로 다시 나눴고, 최종적으로 네 개의 Spring Boot 애플리케이션으로 구성되었습니다. 게이트웨이 역할은 AWS Application Load Balancer가 맡고 있습니다.
문제는 없었을까..?
당연히 있었습니다. 애플리케이션이 네 개나 되다 보니 VM이 더 많이, 더 높은 사양으로 필요해졌고 그만큼 인프라 비용이 올랐습니다. 흔한 구조가 아니라서 LLM 하네스 설정도 조금 복잡했고, CI/CD에 걸리는 시간도 생각보다 길어졌습니다. 작업 환경의 컴퓨팅 파워가 부족한 팀원에게는 IDE에서 Gradle 동기화를 하는 것부터가 큰 난관이었습니다.
문제가 하나 더 있었습니다. 다른 교내 프로젝트들도 멀티모듈을 쓰긴 하지만, 대부분은 모듈마다 별개의 애플리케이션을 두지 않고 루트 모듈이 다른 모듈을 의존성으로 가져가는 구조입니다. 계층을 JAR 단위로 나눈 것에 가깝습니다. 반면 DataGSM은 모듈 하나하나가 독립된 애플리케이션이라, 공통으로 쓰는 파일이 모듈마다 중복으로 존재하게 되었습니다.
공통 모듈 도입
OpenAPI와 Web은 인증 방식도, 구현 방식도, 파라미터와 부가 동작도 다릅니다. 하지만 결과만 놓고 보면 학생 정보는 StudentDto, 동아리 정보는 ClubDto로 똑같이 반환합니다. 그런데 이 소스들이 모듈마다 흩어져 있다 보니 같은 코드를 여러 곳에서 관리해야 하는 비효율이 컸습니다.
그래서 공통 소스를 의존성으로 분리해 한곳에서 관리하려고 datagsm-common 모듈을 추가했습니다. 이 모듈은 실행되는 애플리케이션이 아니라 DTO, Repository, 공통 Config 클래스를 모아 둔 모듈이고, 덕분에 공통 소스의 관리 지점을 하나로 일원화할 수 있었습니다.
SDK까지 한곳에서
그래도 관리 지점이 나뉘어 있는 곳은 여전히 많았습니다. 대표적인 것이 아예 저장소부터 따로 있던 SDK들이었는데, 한 팀원이 이 관리 지점까지 통합하는 방법을 제안해 도입하게 되었습니다.
현재 DataGSM은 TypeScript, JVM, Python SDK를 공식 지원합니다. 이 중 TypeScript와 JVM SDK는 서버가 Kotlin으로 작성되어 있는 덕분에 변환이 쉽습니다. 팀원의 아이디어는 이랬습니다. 서버의 CD 워크플로우에서 각 모듈의 응답 DTO를 직렬화해 라이브러리로 만들고, 이를 비공개 저장소에 배포한 뒤 SDK들이 의존성으로 가져가게 하자는 것입니다. 이렇게 하면 SDK 버전을 따로 올리지 않아도 서버의 응답 형식이 바뀌면 SDK에 바로 반영됩니다.
이를 위해 datagsm-ksp-processor와 datagsm-shared가 추가되었고, SDK에서는 아래처럼 datagsm-shared를 의존성으로 가져갑니다.
dependencies {
api("io.github.themoment-team:datagsm-shared:20261026-53")
}
마무리
그래서 DataGSM은 MSA일까요? 엄밀히 말하면 반쯤입니다. 애플리케이션은 도메인별로 나뉘어 따로 배포되지만, 네 애플리케이션이 하나의 DB를 공유하고 datagsm-common으로 Repository까지 함께 쓰고 있습니다. 서비스마다 데이터 저장소를 따로 두는 일반적인 MSA와는 거리가 있고, 그래서 "제한적인" MSA라고 부르고 있습니다. 교내 프로젝트 규모에서 DB까지 나누는 건 얻는 것보다 관리 부담이 더 크다고 판단했습니다.
그러나 현재 상황에 맞는 적절한 선택지를 골랐다고는 생각하고 있습니다. 지금으로서는 이것이 최선의 방법이었는지 확신할 수는 없지만, 적어도 나쁜 선택과 작업은 아니었다고 생각합니다.