bluetape4k-dependencies 제작기 Part 1: 중앙 BOM이 필요한 이유

라이브러리를 하나만 만들 때는 버전 관리가 단순합니다.
implementation("io.github.bluetape4k:bluetape4k-core:1.11.0")새 버전을 만들고 릴리스 노트를 작성하면 사용자는 해당 버전을 선택할 수 있습니다. 그러나 여러 라이브러리를 독립 저장소에서 배포하면 버전 관리 경계가 달라집니다.
bluetape4k는 projects, exposed, aws, image, text, graph, leader, javers처럼 저장소가 나뉘어
있습니다. 각 저장소는 자기 일정으로 개발되고, 자기 버전으로 배포됩니다. 여기서 라이브러리를 만드는 사람의 질문과
사용하는 사람의 질문이 갈라집니다.
만드는 사람: projects 1.11.0 나왔고, exposed 1.11.0 나왔고, aws 0.4.0 나왔고, image 0.3.0 나왔고...
쓰는 사람: 함께 사용할 수 있는 조합은 무엇인가?이 질문이 나오기 시작하면 버전 관리는 더 이상 build.gradle.kts 한 줄 문제가 아닙니다. 여러 라이브러리를 묶어서
만드는 쪽에서는 “어떤 버전들이 같은 릴리스 계열에 속하는가”를 공개적으로 설명해야 합니다. 그렇지 않으면 사용자가 직접 조합을
맞춰야 합니다. 이는 라이브러리가 공개해야 할 호환 계약을 사용자에게 전가하는 방식입니다.
호환되지 않는 조합에서 NoSuchMethodError가 발생하면 원인 분석 비용도 사용자에게 전가됩니다.
익숙한 예: Spring Boot 의존성 관리
섹션 제목: “익숙한 예: Spring Boot 의존성 관리”JVM 개발자라면 이미 비슷한 모델을 자주 씁니다. Spring Boot를 쓸 때 보통 모든 Spring, Jackson, Tomcat, Netty, Micrometer 버전을 직접 고르지 않습니다.
Gradle에서는 이런 식으로 가져옵니다.
dependencies { implementation(platform("org.springframework.boot:spring-boot-dependencies:4.1.0"))
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-actuator")}Maven에서는 dependencyManagement에서 BOM을 가져옵니다.
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>4.1.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies></dependencyManagement>핵심은 “Spring Boot가 모든 의존성을 대신 선택한다”가 아니라 검증된 조합을 공개한다는 점입니다.
spring-boot-dependencies 4.1.0을 쓰면 -> 이 Spring Boot 계열에서 검증한 의존성 버전 조합을 쓴다.사용자는 각 의존성의 버전을 외우지 않습니다. 대신 spring-boot-dependencies 버전을 고릅니다. 문제가 생기면
“내가 직접 고른 Jackson 버전이 이상한가?”부터 보지 않고, “이 Boot 계열에서 기대하는 의존성 그래프와 다른가?”
부터 확인할 수 있습니다.
bluetape4k-dependencies도 같은 문제를 풀기 위해 만들었습니다. 규모는 Spring Boot보다 훨씬 작지만, 문제의 모양은
비슷합니다.
저장소가 나뉘면 버전도 흩어진다
섹션 제목: “저장소가 나뉘면 버전도 흩어진다”처음에는 각 저장소가 자기 버전만 잘 관리하면 된다고 생각하기 쉽습니다.
bluetape4k-projects -> 1.11.0bluetape4k-exposed -> 1.11.0bluetape4k-aws -> 0.4.0bluetape4k-image -> 0.3.0bluetape4k-text -> 0.2.1bluetape4k-graph -> 0.5.1bluetape4k-leader -> 0.4.0bluetape4k-javers -> 0.2.1저장소 내부에서는 이 방식이 괜찮습니다. aws는 AWS SDK와 Ktor/Spring Boot 경계를 보고, exposed는 Exposed와
DB 드라이버를 보고, leader는 리더 공급자와 스토리지 선택을 봅니다. 각 저장소는 자기 책임을 나눠 갖습니다.
하지만 사용자 프로젝트에서는 이 저장소들이 다시 하나의 클래스 경로에 모입니다.
dependencies { implementation("io.github.bluetape4k:bluetape4k-core:1.11.0") implementation("io.github.bluetape4k.exposed:bluetape4k-exposed-jdbc:1.10.0") implementation("io.github.bluetape4k.leader:bluetape4k-leader-spring-boot:0.3.1")}이 코드는 컴파일될 수도 있습니다. 그래서 더 위험합니다. 컴파일이 안 되면 바로 고치면 됩니다. 컴파일은 되는데 실행 중에 실패하는 조합은 원인 분석 비용이 더 큽니다.
예를 들어 다음 같은 일이 생길 수 있습니다.
Spring Boot 계열은 4.x를 기대한다.어떤 모듈은 아직 Boot 3.x 이름을 가진 아티팩트를 가져온다.다른 모듈은 Ktor 3.x 기준으로 올라갔다.전이 의존성은 서로 다른 Netty/Fabric8/Vert.x 계열을 끌고 온다.이때부터 애플리케이션 코드, 라이브러리 조합, 전이 의존성 선택 결과를 차례로 조사해야 합니다. 중앙 BOM은 이러한 조합 검증 비용을 라이브러리 공급자 측으로 이동시킵니다.
이 문제를 해결하려면 중앙 BOM이 필요합니다.
BOM은 함께 사용할 버전 조합을 공개한다
섹션 제목: “BOM은 함께 사용할 버전 조합을 공개한다”BOM은 사용자가 가져가는 공개 계약입니다.
상위 라이브러리 저장소 -> 각 저장소가 자기 BOM을 배포한다 -> bluetape4k-dependencies가 하위 BOM을 모은다 -> 사용자는 bluetape4k-dependencies 하나를 가져온다흐름을 다이어그램으로 보면 이렇습니다.

bluetape4k-dependencies는 그 조합을 공개 BOM으로 묶습니다. 사용자는 중앙 BOM을 적용하고 각 모듈 의존성에는 버전을 선언하지 않습니다.bluetape4k-dependencies 1.3.0은 이런 식의 조합을 의미합니다.
| 포함된 BOM | 버전 |
|---|---|
io.github.bluetape4k:bluetape4k-bom | 1.11.0 |
io.github.bluetape4k.exposed:bluetape4k-exposed-bom | 1.11.0 |
io.github.bluetape4k.aws:bluetape4k-aws-bom | 0.4.0 |
io.github.bluetape4k.image:bluetape4k-image-bom | 0.3.0 |
io.github.bluetape4k.text:bluetape4k-text-bom | 0.2.1 |
io.github.bluetape4k.graph:bluetape4k-graph-bom | 0.5.1 |
io.github.bluetape4k.leader:bluetape4k-leader-bom | 0.4.0 |
io.github.bluetape4k.javers:bluetape4k-javers-bom | 0.2.1 |
사용자는 이 표를 외울 필요가 없습니다. 이 표를 외우지 않아도 되게 만드는 것이 BOM의 역할입니다.
dependencies { implementation(platform("io.github.bluetape4k:bluetape4k-dependencies:1.3.0"))
implementation("io.github.bluetape4k:bluetape4k-core") implementation("io.github.bluetape4k.exposed:bluetape4k-exposed-jdbc") implementation("io.github.bluetape4k.aws:bluetape4k-aws-spring-boot") implementation("io.github.bluetape4k.leader:bluetape4k-leader-spring-boot")}여기서 각 모듈에 버전을 적지 않습니다. 버전은 BOM이 결정합니다. 이 원칙이 깨지면 다시 사용자 프로젝트가 조합 책임을 가져갑니다.
1.0.0에서 잡은 첫 공개 계약
섹션 제목: “1.0.0에서 잡은 첫 공개 계약”bluetape4k-dependencies 1.0.0을 만들 때 먼저 정해야 했던 것은 자동화가 아니었습니다.
먼저 정해야 했던 것은 “무엇을 공개 계약으로 볼 것인가”였습니다.
bluetape4k-dependencies에는 두 종류의 정보가 있습니다.
| 구분 | 대상 | 결과물 |
|---|---|---|
| BOM | 사용자의 의존성 해석 | io.github.bluetape4k:bluetape4k-dependencies |
| Gradle 버전 카탈로그 | bluetape4k 저장소의 빌드 별칭과 플러그인·라이브러리 버전 | gradle/libs.versions.toml |
둘 다 중요하지만 독자가 다릅니다.
애플리케이션 개발자는 BOM을 적용하면 되며 버전 카탈로그 구조를 알 필요가 없습니다. 반대로 여러 bluetape4k 저장소를
함께 관리하는 개발자는 버전 카탈로그도 확인해야 합니다. Kotlin, Spring Boot, Exposed, Ktor, Jackson, AWS SDK 같은 공통 호환선을
어디서 올릴지 정해야 하기 때문입니다.
처음에는 이 경계가 혼동되기 쉬웠습니다. “버전 정보를 한곳에 둔다”라고 말하면 BOM과 버전 카탈로그를 같은 대상으로 생각하기 쉽지만 실제 쓰임은 다릅니다.
BOM: Maven Central에 올라간다. 애플리케이션과 외부 사용자가 가져온다. 의존성 해석 결과에 직접 영향을 준다.
버전 카탈로그: bluetape4k 저장소들이 빌드 스크립트에서 별칭으로 쓴다. Git 참조로 고정해서 가져간다. Gradle 플러그인·라이브러리 별칭과 공통 버전의 단일 기준 역할을 한다.이 둘을 섞어서 설명하면 사용자와 관리자 모두 혼동합니다. 사용자 문서에 내부 카탈로그 관리 절차부터 설명하면 공개 사용 계약이 흐려집니다. 반대로 여러 저장소를 관리하는 사람에게 BOM 적용법만 설명하면 실제로 어디에서 버전을 올리고 하위 저장소를 어떻게 동기화하는지 전달할 수 없습니다.
이번 제작기에서는 공급자 관점의 작업을 설명합니다. 카탈로그도 다루지만 목적은 여러 저장소의 버전 결정을 어떻게 한곳으로 모으고, 그 결과를 BOM이라는 공개 계약으로 내보낼지 설명하는 것입니다.
Gradle java-platform으로 BOM을 만든다
섹션 제목: “Gradle java-platform으로 BOM을 만든다”구현은 Gradle의 java-platform 플러그인을 사용합니다. 중요한 부분만 줄이면 다음과 같습니다.
plugins { `java-platform` `maven-publish` signing}
javaPlatform { allowDependencies()}
dependencies { api(platform(libs.bluetape4k.bom)) api(platform(libs.bluetape4k.aws.bom)) api(platform(libs.bluetape4k.image.bom)) api(platform(libs.bluetape4k.text.bom)) api(platform(libs.bluetape4k.graph.bom)) api(platform(libs.bluetape4k.leader.bom)) api(platform(libs.bluetape4k.exposed.bom)) api(platform(libs.bluetape4k.javers.bom))
constraints { api(libs.exposed.core) api(libs.fabric8.kubernetes.client) api(libs.ktor.bom) api(libs.netty.bom) api(libs.testcontainers.bom) }}여기서 하위 BOM을 platform(...)으로 가져오는 이유가 드러납니다. 각 저장소가 이미 자기 모듈 묶음을 BOM으로 공개하고
있다면, 중앙 BOM이 모든 아티팩트를 하나씩 다시 나열할 필요는 없습니다.
나쁜 방향: 중앙 BOM이 모든 bluetape4k 아티팩트 버전을 직접 제약 조건으로 반복한다.
나은 방향: 각 저장소 BOM은 자기 모듈을 책임진다. 중앙 BOM은 하위 BOM을 조합하고, 공통 외부 의존성 계열을 맞춘다.반복이 늘어나면 실수도 늘어납니다. 새 모듈이 중앙 BOM 제약 조건에서 누락되거나, 이미 하위 BOM에서 관리하는 아티팩트를 중앙 BOM에서도 다시 관리하면 어느 곳이 단일 기준인지 불분명해집니다.
1.3.0에서 생성된 bluetape4k 아티팩트의 버전 관리를 가져온 하위 BOM에 위임한 것도 같은 이유입니다. 중앙 BOM이 중복 정보를 직접 관리할수록 변경 누락과 기준 불일치 가능성이 커집니다.
공급자의 릴리스 절차가 바뀐다
섹션 제목: “공급자의 릴리스 절차가 바뀐다”BOM을 만들면 사용자는 편해집니다. 대신 공급자의 릴리스 절차는 더 엄격해집니다.
1. 상위 저장소를 배포한다.2. 상위 BOM 아티팩트가 Maven Central에서 조회되는지 확인한다.3. bluetape4k-dependencies가 그 BOM 버전을 가져간다.4. 중앙 BOM을 `build`와 `publishToMavenLocal`로 검증한다.5. Maven Central에 bluetape4k-dependencies를 마지막에 배포한다.6. 워크숍·예제·애플리케이션은 BOM 버전만 올려서 확인한다.이 순서를 지키지 않으면 의존성 해석 실패가 즉시 발생합니다.
dependencies BOM이 aws 0.4.0을 가리킨다.하지만 aws 0.4.0 BOM이 아직 Maven Central에 없다. -> 사용자 프로젝트는 의존성 해석에 실패한다.이 실패는 코드 결함이 아니라 배포 순서 문제입니다. 그러나 사용자는 이를 “BOM을 적용한 뒤 빌드가 실패한다”는 공개 계약의 결함으로 경험합니다.
그래서 bluetape4k-dependencies는 마지막에 배포되어야 합니다. 상위 라이브러리를 먼저 배포하고, 해당 아티팩트가
보이는지 확인하고, 마지막에 중앙 BOM을 내보냅니다.
Maven Central에는 롤백 버튼이 없다
섹션 제목: “Maven Central에는 롤백 버튼이 없다”로컬 빌드에서는 실수해도 대체로 다시 고치면 됩니다. 스냅숏도 다시 만들 수 있습니다. 하지만 릴리스 아티팩트가 Maven Central에 올라가면 같은 버전을 덮어쓸 수 없습니다.
1.3.0을 배포했다.POM 메타데이터나 의존성 조합에 문제가 있다.같은 1.3.0으로 다시 올릴 수 없다. -> 1.3.1 같은 새 버전을 배포해야 한다.이 제약은 운영에 가깝지만, 여러 라이브러리를 만드는 개발자에게는 현실적인 문제입니다. 릴리스 계열에서 하나라도 빠뜨리면 같은 버전으로 다시 게시할 수 없습니다. Maven Central에는 롤백 버튼이 없습니다.
실제로 1.3.1은 1.3.0 직후 메타데이터와 릴리스 지침을 정리한 패치 릴리스였습니다. 코드 기능이 아니라
배포 계약의 반복 실패를 줄인 것입니다. 이 정리로 이후 사용자가 부담할 원인 분석 비용을 줄였습니다.
이 글을 자기 라이브러리에 적용한다면
섹션 제목: “이 글을 자기 라이브러리에 적용한다면”여러 라이브러리를 함께 개발한다면 처음부터 대규모 릴리스 플랫폼을 구축할 필요는 없습니다. 대신 다음 질문으로 공개 계약을 먼저 정의합니다.
| 질문 | 확인할 것 |
|---|---|
| 사용자가 함께 사용해야 하는 라이브러리가 둘 이상인가? | 그렇다면 조합 버전을 문서가 아니라 아티팩트로 제공할 수 있는지 확인한다. |
| 각 저장소가 자기 BOM을 갖고 있는가? | 중앙 BOM이 모든 아티팩트를 반복하지 않고 하위 BOM을 조합할 수 있다. |
| 공개 계약과 내부 빌드 별칭이 섞여 있지 않은가? | BOM과 버전 카탈로그의 독자를 분리한다. |
| 릴리스 순서가 정해져 있는가? | 상위 아티팩트를 확인한 뒤 중앙 BOM을 마지막에 배포한다. |
| 이미 배포된 버전을 덮어쓸 수 있다고 생각하고 있지 않은가? | Maven Central 릴리스는 패치 버전으로 복구한다. |
핵심은 자동화 도구를 많이 만드는 것이 아닙니다. 먼저 “어떤 버전 조합을 사용자에게 약속할 것인가”를 정해야 합니다. 그다음에야 카탈로그 동기화, 아티팩트 조회 확인, 릴리스 점검표가 의미를 갖습니다.
다음 글
섹션 제목: “다음 글”이번 글에서는 bluetape4k-dependencies가 왜 필요했는지와 1.0.0에서 어떤 공개 계약을 잡았는지 봤습니다.
다음 글에서는 1.3.0까지 오면서 상위 아티팩트의 공개가 지연되거나, 카탈로그와 BOM의
역할을 혼합해 설명하거나, 릴리스 순서를 사람의 기억에 의존했던 문제를 어떻게 보완했는지 정리합니다.
시리즈 글
섹션 제목: “시리즈 글”사용자 관점에서 bluetape4k-dependencies를 적용하는 방법부터 확인하려면
사용 가이드를 먼저 읽으면 됩니다.
댓글
GitHub 계정으로 의견을 남기거나 reaction을 남길 수 있습니다.