Bluetape4k Projects Part 6: Spring Boot 4와 Ktor

이 글은 bluetape4k-projects 시리즈의 마지막 편입니다. 앞에서는 foundation, coroutine/testing, I/O,
data/infra, utility를 차례로 봤습니다. 이제 그 조각들이 실제 application boundary에서 어떻게 보이는지
봅니다. Spring Boot 4와 Ktor 3 모듈입니다.
projects의 application 모듈은 framework를 대체하려 하지 않습니다. Spring Boot는 Spring Boot답게,
Ktor는 Ktor답게 둡니다. 대신 공통 JSON, coroutine helper, Redis serializer, R2DBC/MongoDB coroutine
extension, observability, resilience, testing helper를 service entrypoint 가까이에 정리합니다. 목표는
“숨겨진 마법”이 아니라 “반복 wiring을 줄이고 경계를 명시하기”입니다.

Spring Boot 4: versionless 표준 경로
섹션 제목: “Spring Boot 4: versionless 표준 경로”현재 spring-boot/* 모듈은 Spring Boot 4.x 기준입니다. README도 versionless spring-boot/* artifact가
Spring Boot 4 구현으로 게시된다고 명시합니다. 중요한 운영 규칙도 있습니다. Spring Boot BOM은
dependencyManagement { imports } 대신 Gradle implementation(platform(...)) 방식으로 적용해야 Kotlin
Gradle Plugin 2.3.x 충돌을 피할 수 있습니다.
dependencies { implementation(platform("org.springframework.boot:spring-boot-dependencies:4.x.x")) implementation(platform("io.github.bluetape4k:bluetape4k-bom:<version>")) implementation("io.github.bluetape4k:bluetape4k-spring-boot-core")}bluetape4k-spring-boot-core는 WebFlux + coroutines, RestClient suspend DSL, WebClient extension,
Jackson 2 customizer, Retrofit2 integration, WebTestClient test helper를 제공합니다. Spring Boot 4가
내부적으로 Jackson 2를 쓰므로 이 모듈도 Jackson 2 경계가 기본입니다. Jackson 3은 Part 3의 I/O 계열에서
별도로 다룰 수 있지만, Spring Boot 4 application boundary에서는 Jackson 2라는 현실을 받아들이는 편이
낫습니다. 그래야 application boundary와 framework 내부 경계가 같은 방향을 봅니다.
val restClient = RestClient.create("https://api.example.com")
val user: User = restClient.suspendGet("/users/1")val created: User = restClient.suspendPost("/users", newUser, MediaType.APPLICATION_JSON)Spring Data 계열은 더 좁은 모듈로 나뉩니다. spring-boot/redis는 RedisTemplate과
ReactiveRedisTemplate용 RedisBinarySerializer, RedisCompressSerializer, redisSerializationContext {}
DSL을 제공합니다. spring-boot/r2dbc는 R2dbcEntityOperations coroutine CRUD helper를 제공합니다.
MongoDB와 Cassandra도 Spring Data reactive/coroutine 경계를 정리합니다. Hibernate Lettuce는 Hibernate
second-level cache와 Lettuce Near Cache auto-configuration을 application boundary로 올립니다.
Ktor 3: 명시적인 plugin 조립
섹션 제목: “Ktor 3: 명시적인 plugin 조립”Ktor 모듈은 Spring Boot와 다르게 “명시적 설치”를 기본 태도로 둡니다. ktor/core는 JSON 기본값,
installBluetape4kKtorCore(), JSON 오류 응답, /healthz, /readyz, query/path parameter helper를
제공합니다. 애플리케이션이 plugin 소유권을 직접 가져야 하면 config로 각 기능을 끌 수 있습니다.
fun Application.module() { installBluetape4kKtorCore()
routing { get("/items/{type}") { val type = call.requiredPathParameter("type") val size = call.intQueryParameter("size", defaultValue = 10, range = 1..100) call.respond(mapOf("type" to type, "size" to size)) } }}ktor/observability는 sanitized correlation ID, call logging MDC, Micrometer metrics, Prometheus scrape route
helper를 제공합니다. OpenTelemetry tracing은 기본 설치하지 않습니다. exporter 정책은 애플리케이션이
소유해야 하기 때문입니다. 이 선을 지켜야 합니다. library가 observability backend까지 몰래 정하면 운영자는
나중에 exporter 소유권을 다시 추적해야 합니다.
ktor/resilience4j는 route-scoped helper입니다. caller가 소유한 retry, circuit breaker, rate limiter,
time limiter를 KtorResiliencePolicies로 전달하고, resilientGet, resilientPost, withKtorResilience
로 suspend block을 보호합니다. CancellationException은 circuit breaker 실패로 삼키지 않고 다시 던집니다.
coroutine cancellation을 resilience 실패로 기록하면 metric이 실제 실패를 왜곡합니다.
언제 Spring Boot, 언제 Ktor인가
섹션 제목: “언제 Spring Boot, 언제 Ktor인가”둘 중 하나가 항상 우월하지 않습니다. application boundary가 무엇을 더 필요로 하는지 봐야 합니다.
| 필요 | Spring Boot 4 쪽 | Ktor 3 쪽 |
|---|---|---|
| Spring Data, auto-configuration, enterprise integration | 자연스러운 기본 선택 | 직접 wiring이 많아질 수 있음 |
WebFlux coroutine controller와 WebTestClient | spring-boot/core가 바로 맞음 | 필요하면 Ktor로 작게 구성 |
| Redis serializer, R2DBC/MongoDB coroutine extension | Spring Data 모듈과 잘 맞음 | infra 모듈을 직접 조립 |
| 작은 HTTP API, 명시적 plugin, 얇은 runtime | 다소 무거울 수 있음 | ktor/core가 잘 맞음 |
| route 단위 resilience/observability | Spring ecosystem 설정과 결합 | ktor/observability, ktor/resilience4j가 명시적 |
| 테스트 스타일 | Spring context + slice/integration test | testApplication 중심의 빠른 route test |
Spring Boot는 ecosystem integration이 강합니다. 이미 Spring Data, auto-configuration, actuator, enterprise library가 많은 서비스라면 억지로 Ktor로 줄일 필요가 없습니다. Ktor는 작은 HTTP API, 명시적 plugin, 가벼운 runtime boundary가 중요한 서비스에서 좋습니다. 둘을 비교할 때 “취향”만 이야기하면 결론이 잘 안 납니다. configuration 소유권, test 속도, data access 모양, observability 정책을 놓고 보면 대개 답이 나옵니다.
wiring checklist
섹션 제목: “wiring checklist”새 backend service에 projects를 적용한다면 순서를 이렇게 잡는 편이 안전합니다.
bluetape4k-bom과 framework BOM을platform(...)으로 가져옵니다.- Spring Boot 또는 Ktor 중 application boundary를 먼저 고릅니다.
- HTTP 경계의 JSON/error/health/readiness policy를 고정합니다.
- data/Redis/messaging 모듈은 실제 외부 시스템 하나씩 붙입니다.
- observability는 correlation ID, metric, trace 소유권을 명확히 나눕니다.
- resilience는 timeout, retry, circuit breaker가 같은 이야기를 하도록 한 경로부터 적용합니다.
- 예제와 workshop에서 가장 가까운 application 모양을 찾아 test fixture까지 확인합니다.
이 순서를 지키면 application module은 얇게 남습니다. 순서를 건너뛰면 controller가 serializer와 retry와 pool과 metric까지 모두 떠안습니다. 그런 코드는 변경에 약합니다.
참고 링크
섹션 제목: “참고 링크”- Spring Boot root:
spring-boot/ - Ktor root:
ktor/ - Examples root:
examples/ - Workshop root: bluetape4k-workshop
- Spring Boot core README:
spring-boot/core/README.ko.md - Ktor core README:
ktor/core/README.ko.md - Ktor observability README:
ktor/observability/README.ko.md
마무리
섹션 제목: “마무리”bluetape4k-projects 시리즈는 공통 foundation에서 시작해 application boundary까지 올라왔습니다. 이
저장소의 역할은 모든 문제를 대신 해결하는 거대한 framework가 아닙니다. Kotlin/JVM backend에서 반복되는
작은 경계들을 같은 방식으로 다루게 해 주는 공통 기반입니다.
Spring Boot 4와 Ktor 3 모듈은 그 기반을 service entrypoint에 올리는 마지막 층입니다. 하나는 ecosystem integration을, 다른 하나는 명시적이고 얇은 runtime을 더 잘 보여줍니다. 중요한 것은 둘 중 무엇을 고르든 BOM, module selection, configuration ownership, observability, resilience, test fixture를 같은 기준으로 검증하는 것입니다.
시리즈의 결론은 단순합니다. 필요한 조각만 가져오고, 경계를 숨기지 말고, 예제로 사용 모양을 확인하고,
테스트로 정책을 고정합니다. 이 정도면 projects는 서비스 코드 뒤에서 반복되는 경계를 맡는 기반으로 남습니다.
기반이 할 일을 분명히 하면 애플리케이션 코드는 도메인 로직에 집중하고, 운영자는 실패가 어디서 시작됐는지
더 빨리 확인합니다.
댓글
GitHub 계정으로 의견을 남기거나 reaction을 남길 수 있습니다.