가장 큰 장점을 얘기해 보면 아래 3개정도로 요약 할 수 있을 것 같다
1. null 안전성과 편한 사용
// 컴파일 시점에 null 체크
fun processUser(user: User?) {
// null이면 메서드 실행 안함
user?.process()
// null이면 기본값 사용
return user?.name ?: "Unknown"
}
// Java
public String getUserName(User user) {
return Optional.ofNullable(user)
.map(User::getName)
.orElse("Unknown");
}
2. Lombok 제거
// Data Class로 보일러플레이트 제거
// java record 도 있지만 불변이기도 하고 data가 상속도 되는 등 확장도 가능함
data class User(
val id: Long,
val name: String,
val email: String? = null, // 기본값 설정 가능
var status: String // 수정 가능
)
// Java
public class User {
private Long id;
private String name;
private String email;
// getter, setter, equals, hashCode, toString 모두 필요
}
3. Coroutine
// 비동기 프로그래밍을 위한 코루틴
// CompletableFuture보다 직관적인 비동기 코드 작성
// Kotlin
suspend fun fetchUser(): User {
return coroutineScope {
val user = async { userRepository.findById(1) }
user.await()
}
}
// Java
CompletableFuture<User> fetchUser() {
return CompletableFuture.supplyAsync(() ->
userRepository.findById(1));
}
여기서 코루틴과 기존 Java에서의 Webflux를 비교한다면 장점이 더 잘보인다.
1. 단순 비동기 처리
// Java Reactor
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable Long id) {
return userRepository.findById(id)
.flatMap(user -> Mono.just(enrichUser(user)))
.defaultIfEmpty(User.empty())
.doOnError(e -> log.error("Error fetching user", e));
}
// Kotlin Coroutine
@GetMapping("/users/{id}")
suspend fun getUser(@PathVariable id: Long): User {
return try {
val user = userRepository.findById(id)
enrichUser(user) ?: User.empty()
} catch (e: Exception) {
log.error("Error fetching user", e)
throw e
}
}
2. 여러 API 병렬 호출
// Java Reactor
public Mono<OrderDetails> getOrderDetails(Long orderId) {
return Mono.zip(
orderRepository.findById(orderId),
paymentService.getPaymentInfo(orderId),
shippingService.getShippingStatus(orderId)
).map(tuple -> new OrderDetails(
tuple.getT1(),
tuple.getT2(),
tuple.getT3()
)).onErrorResume(e -> Mono.empty());
}
// Kotlin Coroutine
suspend fun getOrderDetails(orderId: Long): OrderDetails {
return coroutineScope {
val order = async { orderRepository.findById(orderId) }
val payment = async { paymentService.getPaymentInfo(orderId) }
val shipping = async { shippingService.getShippingStatus(orderId) }
OrderDetails(
order = order.await(),
payment = payment.await(),
shipping = shipping.await()
)
}
}
3. 스트림 처리
// Java Reactor
@GetMapping("/prices/stream")
public Flux<Price> streamPrices() {
return priceRepository.findAll()
.filter(price -> price.getValue() > 1000)
.map(this::enrichPrice)
.buffer(10)
.delayElements(Duration.ofMillis(100))
.doOnError(e -> log.error("Error in price stream", e))
.onErrorReturn(Price.empty());
}
// Kotlin Flow
@GetMapping("/prices/stream")
fun streamPrices(): Flow<Price> = flow {
priceRepository.findAll()
.filter { it.value > 1000 }
.map { enrichPrice(it) }
.chunked(10)
.collect { chunk ->
delay(100)
emit(chunk)
}
}.catch { e ->
log.error("Error in price stream", e)
emit(Price.empty())
}
생각보다 어렵지 않으니 고민 중이라면 시작하고 고민해보자~
'개발 > 백엔드' 카테고리의 다른 글
Spring 대용량 엑셀 업로드 (0) | 2025.02.28 |
---|---|
내가 보려고 작성하는 각종 docker 설치 명령어 - mysql, redis, kafka, mongo (0) | 2025.02.14 |
Spring Security OAuth (0) | 2025.01.07 |
Spring GraphQL (1) | 2025.01.06 |
빠르게 GraphQL 기본 개념 정리 (1) | 2025.01.06 |