이 글에서 얻는 것
- Gradle/Maven을 “명령어만 치는 도구”가 아니라, 의존성 그래프를 관리하는 시스템으로 이해합니다.
- 버전 충돌(전이 의존성)과 ‘왜 저 라이브러리가 들어왔지?’ 문제를 추적하는 기본 루틴이 생깁니다.
- 멀티모듈 분리를 언제 해야 하는지, 그리고 분리했을 때 무엇이 좋아지고 무엇이 어려워지는지 감각을 잡습니다.
0) 빌드 도구를 알아야 하는 이유(백엔드 실무)
실무에서 자주 만나는 문제:
- “어제는 되던 빌드가 오늘 깨졌다”(의존성/캐시/환경)
- “취약점 패치하려고 올렸더니 다른 게 깨졌다”(전이 의존성 충돌)
- “테스트는 통과하는데 배포에서만 실패한다”(프로파일/리소스/환경 차이)
이런 문제는 코드보다 “빌드/의존성/환경”에서 터지는 경우가 많습니다.
1) 의존성 그래프: 직접 의존성과 전이 의존성
대부분의 문제는 전이 의존성에서 시작합니다.
- 내가 추가한 건 A 하나인데,
- A가 B/C/D를 끌고 오고,
- 그중 하나가 기존 버전과 충돌합니다.
그래서 빌드 도구를 쓸 때의 기본 습관은:
- “최종적으로 어떤 버전이 선택됐는지”를 확인하고,
- “왜 그 버전이 선택됐는지”를 설명할 수 있어야 합니다.
2) 버전 충돌을 추적하는 기본 커맨드
Gradle(자주 쓰는 것):
./gradlew dependencies./gradlew dependencyInsight --dependency <name> --configuration runtimeClasspath
Maven(자주 쓰는 것):
mvn dependency:tree -Dincludes=<groupId>:<artifactId>
팁:
- 문제가 런타임에서만 터지면
runtimeClasspath(또는 runtime scope)를 보세요. - 테스트에서만 터지면 test scope 의존성도 확인해야 합니다.
3) BOM/Dependency Management: 버전은 ‘중앙에서’ 관리하는 편이 안전하다
버전이 여기저기 흩어지면 결국 꼬입니다.
- Spring Boot는 BOM을 통해 권장 버전을 정해줍니다.
- 조직/프로젝트도 “의존성 버전 정책”을 한 곳에 모으는 편이 운영이 쉽습니다.
실무 감각:
- “각 모듈이 각자 버전을 들고 있는 구조”는 시간이 갈수록 충돌과 취약점 대응 비용이 급증합니다.
4) 재현 가능한 빌드: CI가 기준이다
로컬에서만 되는 빌드는 결국 사고로 이어집니다.
- JDK 버전 고정(예: toolchain)
- 의존성 고정(락파일/버전 정책)
- 캐시가 깨져도 다시 만들 수 있어야 함(클린 빌드가 돌아야 함)
5) 멀티모듈: “코드가 커질 때”가 아니라 “경계를 강제해야 할 때”
멀티모듈이 유리해지는 신호:
- 빌드/테스트 시간이 너무 길어졌다
- 모듈 간 순환 의존이 자주 생긴다(구조로 막고 싶다)
- 팀이 커져서 같은 코드에서 충돌이 잦다
- 도메인/기능 경계를 강제하고 싶다(캡슐화)
하지만 대가도 있습니다.
- 설정/빌드 복잡도 증가
- 공통 코드 분리/의존성 방향 관리 필요
“무조건 멀티모듈”이 아니라, 문제를 해결하는 수단으로 도입하는 편이 좋습니다.
6) 운영에서 자주 터지는 포인트
- 의존성 충돌: 런타임에
NoSuchMethodError,ClassNotFoundException - 리소스/프로파일: 로컬과 서버의 설정 로딩 차이
- 테스트 격리: 테스트가 서로 상태를 공유해 flaky해짐
- CI 캐시: 캐시가 오염돼 “깨졌다/됐다” 반복(캐시 키/무효화 전략 필요)
연습(추천)
- 프로젝트에서 임의로 버전 충돌을 만들어보고(의존성 2개가 같은 라이브러리를 다른 버전으로 요구),
dependencyInsight/dependency:tree로 원인을 찾아 해결해보기 - “빌드는 되는데 런타임 에러”를 가정하고, runtimeClasspath 기준으로 의존성을 점검하는 루틴을 만들어보기
- 작은 모듈을 하나 분리해 멀티모듈로 바꿔보고, 빌드 속도/의존성 규칙이 어떻게 달라지는지 비교해보기
💬 댓글