콘텐츠로 이동
Bluetape4k 문서1.11

핵심 Kotlin 라이브러리

최신 안정판 Bluetape4k 1.11.0 릴리스 기준

backend 모듈은 일관된 exception type을 쓰는 parameter validation, byte-safe encoder, bounded collection, 날짜와 시간 helper를 반복해서 필요로 합니다. bluetape4k-core는 이런 기반 기능을 모아 상위 모듈이 조금씩 다른 구현을 다시 만들지 않게 합니다.

애플리케이션이나 다른 library가 여러 기반 type을 함께 사용할 때 core를 추가합니다. JDK나 Kotlin 한 줄로 계약이 분명하게 끝난다면 그 표현을 우선합니다. core의 가치는 여러 모듈이 같은 failure와 lifecycle 계약을 공유할 때 커집니다.

dependencies {
implementation(platform("io.github.bluetape4k:bluetape4k-dependencies:<version>"))
implementation("io.github.bluetape4k:bluetape4k-core")
}

저장소 기준은 Java 21과 Kotlin 2.3입니다. 여러 상위 bluetape4k 모듈이 core를 API dependency로 사용합니다.

이 모듈은 하나의 runtime subsystem이 아니라 toolbox입니다. 주요 영역은 support validation과 extension, codec encoder, collections bounded/paginated container, range value type, concurrent helper, functional adapter, time DSL, reflection과 Apache Commons bridge입니다.

validation 이름은 failure 의미를 드러냅니다. 새 require* helper는 caller 입력을 IllegalArgumentException으로 거부합니다. BoundedStack, RingBuffer 같은 collection은 capacity가 계약에 포함됩니다.

Core를 API 이름순으로 읽기보다 경계가 실행 흐름을 따라 이동하는 순서로 읽습니다.

Core boundary validation map

설계 질문결정할 계약
잘못된 caller 입력을 어디서 끊을까?검증과 불변식exception type, parameter name, non-null 내부 모델
bytes를 text로 어떻게 옮길까?Encoding과 데이터 경계charset, URL-safe Base64/Hex, malformed input
최근 N개를 어떤 순서로 유지할까?Bounded collectionscapacity, eviction, stack/ring read order
시간 조회의 끝을 포함할까?시간과 범위endpoint inclusion, overlap, timezone
active와 waiting work를 어디까지 허용할까?Concurrency와 lifecyclerejection, cancellation, close order
위 계약을 한 component로 어떻게 묶을까?Core 실전 레시피end-to-end test와 운영 signal
import io.bluetape4k.codec.encodeBase64String
import io.bluetape4k.support.requireNotBlank
fun tokenFor(userId: String?): String {
val id = userId.requireNotBlank("userId")
return id.encodeBase64String()
}

requireNotBlank는 검증한 값을 반환합니다. 별도의 !! 없이 expression 안에서 validation을 끝낼 수 있습니다.

작업시작 지점
nullable string과 caller argument 검증io.bluetape4k.support.RequireSupport extension
Base64, Base58, Base62, Hex, URL62 encode/decodeio.bluetape4k.codec
bounded LIFO 또는 circular window 유지BoundedStack, RingBuffer
page나 lazy permutation 표현PaginatedList, permutation extension
open/closed endpoint를 가진 range 표현io.bluetape4k.range type
duration, period, temporal, quarter 연산io.bluetape4k.time
명시적인 close 계약을 가진 concurrent reduceConcurrentReducer
요구 사항권장 선택선택하지 말아야 할 경우
nullable 입력을 검증한 뒤 같은 값을 계속 사용requireNotNull, requireNotBlank, requirePositiveNumber 계열내부 불변식 검증에는 사용하지 않습니다. caller 입력 오류를 뜻하는 IllegalArgumentException 계약입니다.
최근 값을 LIFO로 보관BoundedStack시간순 재생이 필요하면 RingBuffer가 맞습니다.
최근 N개를 입력 순서로 보관RingBufferstack의 top/pop 의미가 필요하면 BoundedStack을 사용합니다.
비동기 요청의 동시 실행 수와 대기열을 함께 제한ConcurrentReducersuspend 함수의 동시성만 제한한다면 coroutine semaphore나 mapParallel이 더 자연스럽습니다.
JVM 종료 시 전역 자원을 역순으로 정리ShutdownQueuerequest나 bean lifecycle처럼 더 이른 종료 시점이 있다면 그 lifecycle에서 직접 닫습니다.

1. 경계에서 검증하고 내부 타입을 단순하게 유지하기

섹션 제목: “1. 경계에서 검증하고 내부 타입을 단순하게 유지하기”
import io.bluetape4k.support.requireNotBlank
import io.bluetape4k.support.requirePositiveNumber
data class PageRequest(val cursor: String, val size: Int)
fun pageRequest(cursor: String?, size: Int): PageRequest =
PageRequest(
cursor = cursor.requireNotBlank("cursor"),
size = size.requirePositiveNumber("size"),
)

require* 함수는 검증한 receiver를 반환하므로 validation 이후 코드에서 nullable 분기나 !!가 필요 없습니다. 메시지의 parameter name은 API 이름과 맞춰야 운영 로그에서 어느 입력이 잘못됐는지 바로 찾을 수 있습니다.

2. bounded collection의 순서를 의도적으로 선택하기

섹션 제목: “2. bounded collection의 순서를 의도적으로 선택하기”
val undo = BoundedStack<String>(maxSize = 3)
undo.pushAll("v1", "v2", "v3", "v4")
undo.toList() // [v4, v3, v2] — top에서 bottom 순서
val recent = RingBuffer<String>(maxSize = 3)
recent.addAll("v1", "v2", "v3", "v4")
recent.toList() // [v2, v3, v4] — 가장 오래된 값부터 순서

두 타입 모두 용량을 넘으면 가장 오래된 값을 버리지만 읽는 방향이 다릅니다. BoundedStack.pop()은 최신 값을 제거하고, RingBuffer.drop(n)은 오래된 값부터 제거합니다. 둘 다 process-local 자료구조이며 durable queue나 분산 backpressure를 대신하지 않습니다.

3. 외부 비동기 API 앞에 명시적인 용량 제한 두기

섹션 제목: “3. 외부 비동기 API 앞에 명시적인 용량 제한 두기”
import io.bluetape4k.concurrent.concurrentReducerOf
import java.util.concurrent.CompletionStage
fun <T> fetchBounded(
ids: List<String>,
fetchAsync: (String) -> CompletionStage<T>,
): List<T> = concurrentReducerOf<T>(
maxConcurrency = 8,
maxQueueSize = 64,
).use { reducer ->
ids.map { id -> reducer.add { fetchAsync(id) } }
.map { promise -> promise.join() }
}

add는 queue가 가득 차거나 reducer가 닫힌 경우 호출 시점에 직접 throw하지 않습니다. 각각 CapacityReachedException 또는 RejectedExecutionException으로 완료된 CompletableFuture를 반환합니다. 따라서 반환 promise를 반드시 관찰해야 합니다. close()는 대기 중 작업을 취소하지만 이미 실행 중인 외부 CompletionStage까지 강제로 중단하지는 않습니다.

public boundary에서 입력을 검증하고 내부에는 non-null 값을 넘깁니다. codec은 domain logic 곳곳이 아니라 transport나 storage 경계에서 사용합니다. 작업량이 계속 쌓일 수 있다면 unbounded collection 대신 capacity를 가진 type을 선택합니다. executor나 queue를 소유하는 helper는 use 또는 try-finally에서 닫습니다.

Kotlin stdlib, Java time/reflection/concurrency, Apache Commons, Eclipse Collections, hashing utility를 감싸거나 보완합니다. 상위 bluetape4k 모듈 public API가 core type을 노출할 수 있습니다. source에서 core API를 직접 import한다면 transitive dependency에 기대지 말고 직접 선언하는 편이 명확합니다.

global 설정 파일은 없습니다. collection capacity, charset, range boundary, timeout 같은 constructor argument와 function parameter로 동작을 선택합니다. 이 값은 관련 없는 global state가 아니라 component를 소유한 설정 가까이에 둡니다.

validation helper는 잘못된 caller 입력에 IllegalArgumentException을 던집니다. codec decoder는 malformed input 오류를 underlying codec 계약에 따라 전달합니다. bounded collection은 잘못된 capacity를 생성 시점에 거부합니다. ConcurrentReducer.close()는 queue의 작업을 취소하고 이후 submission을 거부합니다.

증상먼저 확인할 것대응
validation 예외의 값이 예상과 다름체이닝 앞 단계에서 receiver가 변환됐는지, parameter name이 맞는지경계에서 원본을 한 번 검증하고 변환을 뒤로 옮깁니다.
최근 항목의 순서가 반대로 보임BoundedStack.toList()는 최신순, RingBuffer.toList()는 오래된 순인지undo/history 요구를 구분해 타입을 바꿉니다.
ConcurrentReducer 결과가 CompletionException으로 실패cause가 CapacityReachedException, task 예외, null stage인지queue 포화는 retry/429 같은 overload 정책으로 처리하고 task failure와 분리합니다.
shutdown 후 새 작업이 계속 들어옴reducer promise의 RejectedExecutionException을 무시하는지모든 반환 future를 관찰하고 producer를 먼저 중단한 뒤 reducer를 닫습니다.
ShutdownQueue 자원 순서가 중요함등록 순서의 역순(LIFO)으로 close되는지의존 자원을 먼저 등록하고 그 자원을 사용하는 wrapper를 나중에 등록합니다.

대부분의 helper는 background service를 소유하지 않습니다. executor, queue, 큰 buffer를 감싸는 utility는 별도로 봐야 합니다. workload 근거로 capacity를 정하고 service lifecycle에 close/shutdown을 연결합니다. reflection helper를 hot path에 넣기 전에는 실제로 측정합니다.

테스트는 package와 계약 단위로 나뉩니다. BoundedStackTest, PaginatedListTest, codec/range/time test, ConcurrentReducer test를 시작점으로 삼을 수 있습니다.

Terminal window
./gradlew :bluetape4k-core:test --no-configuration-cache

특정 helper를 도입할 때는 전체 모듈을 하나의 integration으로 취급하지 말고 대응하는 가장 작은 test pattern을 참고합니다.

toolbox 전체를 다루는 단일 workshop은 없습니다. 상위 repository example이 core를 transitive하게 사용합니다. 한 기능을 익히려면 대응하는 unit test의 assertion 하나를 작은 실행 예제로 바꾸는 방법이 가장 빠릅니다.

core의 API는 범위가 넓어 lifecycle과 성능 특성이 모두 같지 않습니다. 선택한 family의 source와 test를 확인해야 합니다. encoding은 암호화가 아니고, reflection helper가 비공개 API를 안정된 계약으로 바꾸지도 않으며, bounded container는 distributed backpressure를 제공하지 않습니다.

아래 그림은 1.11.0 배포본의 README 자산을 해당 배포 커밋에서 직접 불러옵니다. 이후 SNAPSHOT이 아니라 이 매뉴얼 버전의 구조와 실행 흐름을 보여 줍니다. 미리보기를 누르면 같은 배포 커밋의 SVG 원본이 열립니다.

bluetape4k-core 모듈 구성 개요 다이어그램

섹션 제목: “bluetape4k-core 모듈 구성 개요 다이어그램”

bluetape4k-core 모듈 구성 개요 다이어그램

배포본 README: bluetape4k/core/README.ko.md

bluetape4k-core 핵심 클래스 구조 다이어그램

섹션 제목: “bluetape4k-core 핵심 클래스 구조 다이어그램”

bluetape4k-core 핵심 클래스 구조 다이어그램

배포본 README: bluetape4k/core/README.ko.md

RequireSupport Validation 체이닝 흐름 다이어그램

섹션 제목: “RequireSupport Validation 체이닝 흐름 다이어그램”

RequireSupport Validation 체이닝 흐름 다이어그램

배포본 README: bluetape4k/core/README.ko.md