여기서는 일단 Spring MVC 를 사용한 방법으로 작성
Kotlin과 코루틴을 사용하려면 build.gradle.kts 파일에 필요한 의존성을 추가
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.6.0")
implementation("org.springframework.boot:spring-boot-starter-webflux") // 선택사항, WebFlux 사용 시
}
서비스 레이어에서 suspend 함수를 사용하여 비동기 작업을 수행. 이는 비즈니스 로직을 처리하는 부분임.
import kotlinx.coroutines.delay
import org.springframework.stereotype.Service
@Service
class MyService {
suspend fun performAsyncOperation(): String {
delay(1000L) // 1초 동안 지연
return "Result from async operation"
}
suspend fun performMultipleAsyncOperations(): String = coroutineScope {
val deferred1 = async { asyncOperation1() }
val deferred2 = async { asyncOperation2() }
val result1 = deferred1.await()
val result2 = deferred2.await()
return@coroutineScope "$result1 and $result2"
}
private suspend fun asyncOperation1(): String {
delay(1000L)
return "Result from async operation 1"
}
private suspend fun asyncOperation2(): String {
delay(1000L)
return "Result from async operation 2"
}
}
위 코드에서 performAsyncOperation과 performMultipleAsyncOperations는 suspend 함수로 정의되어 있으며, delay와 async를 사용하여 비동기 작업을 처리
컨트롤러 레이어에서는 suspend 함수를 호출하여 비동기 작업을 처리. Spring MVC는 비동기 서블릿을 통해 suspend 함수를 지원함.
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class MyController(private val myService: MyService) {
@GetMapping("/async-operation")
suspend fun asyncOperation(): String {
return myService.performAsyncOperation()
}
@GetMapping("/multiple-async-operations")
suspend fun multipleAsyncOperations(): String {
return myService.performMultipleAsyncOperations()
}
}
위 코드에서 asyncOperation과 multipleAsyncOperations는 suspend 함수로 정의되어 있으며, 서비스 레이어의 suspend 함수를 호출.
suspend 함수를 지원.runBlocking은 호출된 스레드를 블로킹하므로, 실제 애플리케이션 코드에서는 사용하지 않는 것이 좋음. 가급적 테스트 코드에서만 사용하여 코루틴을 동기식으로 실행하고 결과를 검증할 수 있음테스트 코드에서는 runBlocking을 사용하여 코루틴을 실행하고 결과를 검증할 수 있음
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class MyServiceTest {
private val myService = MyService()
@Test
fun testPerformAsyncOperations() = runBlocking {
val result = myService.performMultipleAsyncOperations()
assertEquals("Result from async operation 1 and Result from async operation 2", result)
}
}
위 테스트 코드는 runBlocking을 사용하여 suspend 함수를 호출하고, 비동기 작업의 결과를 검증.
¶ Tomcat, Netty, Spring MVC와 WebFlux
Tomcat과 Netty에서의 지원 여부
Tomcat
- Spring MVC: Tomcat은 전통적인 서블릿 컨테이너로서, Spring MVC의 동기 및 비동기 요청 처리를 모두 지원합니다. 서블릿 3.1+의 비동기 기능을 통해
suspend함수를 지원할 수 있습니다.- WebFlux: Tomcat은 Spring WebFlux에서도 사용할 수 있지만, 기본적으로 서블릿 기반의 비동기 처리 방식을 따릅니다. 완전한 논블로킹 IO는 지원하지 않지만, 서블릿 비동기 기능을 통해 비동기 작업을 처리할 수 있습니다.
Netty
- Spring MVC: Netty는 주로 WebFlux와 함께 사용됩니다. Spring MVC와 함께 사용하기에는 적합하지 않습니다.
- WebFlux: Netty는 Spring WebFlux와 함께 사용할 때 최고의 성능을 발휘합니다. Netty는 논블로킹 IO를 완전히 지원하며, 고성능 비동기 네트워크 애플리케이션에 적합합니다. Spring WebFlux의 기본 서버 구현체로 많이 사용됩니다.
Spring Boot 개발 시 유의점
서버 선택
- Tomcat: 전통적인 동기식 애플리케이션 및 부분적으로 비동기 처리가 필요한 애플리케이션에 적합합니다. 서블릿 3.1+의 비동기 기능을 통해 비동기 작업을 처리할 수 있지만, 완전한 논블로킹 IO를 지원하지는 않습니다.
- Netty: 완전한 논블로킹 IO를 지원하며, 고성능 비동기 애플리케이션에 적합합니다. Spring WebFlux와 함께 사용할 때 최고의 성능을 제공합니다.
Spring MVC vs WebFlux
- Spring MVC: 동기 및 비동기 요청 처리를 지원하며, 전통적인 웹 애플리케이션 개발에 적합합니다. Tomcat과 같은 서블릿 컨테이너에서 잘 동작합니다.
- WebFlux: 완전한 논블로킹 IO를 지원하며, 고성능 비동기 애플리케이션에 적합합니다. Netty와 같은 논블로킹 IO 서버와 함께 사용할 때 최고의 성능을 발휘합니다.
코루틴 사용
- suspend 함수: Spring MVC와 WebFlux에서 모두
suspend함수를 사용할 수 있습니다. Spring MVC는 서블릿 3.1+의 비동기 기능을 통해, WebFlux는 논블로킹 IO를 통해 이를 지원합니다.- runBlocking 지양:
runBlocking은 호출된 스레드를 블로킹하므로, 실제 애플리케이션 코드에서는 사용하지 않는 것이 좋습니다. 대신 테스트 코드에서만 사용하여 코루틴을 동기식으로 실행하고 결과를 검증합니다.성능 및 확장성 고려
- 비동기 작업 처리: 비동기 작업이 많은 경우, WebFlux와 Netty 조합을 사용하는 것이 성능에 유리합니다. 이는 논블로킹 IO와 고성능 네트워크 처리를 지원하기 때문입니다.
- 점진적 마이그레이션: 기존 Spring MVC 애플리케이션을 비동기로 전환할 때는, 점진적으로
suspend함수를 도입하고, 필요 시 WebFlux로 전환하는 전략을 사용할 수 있습니다.적절한 아키텍처 설계
- 서비스 레이어에서 코루틴 사용: 서비스 레이어에서
suspend함수와coroutineScope를 사용하여 비동기 작업을 처리합니다. 컨트롤러 레이어에서는 이를 호출하여 비동기 응답을 반환합니다.- 레이어 분리: 비즈니스 로직과 비동기 처리 로직을 분리하여 코드의 가독성과 유지보수성을 높입니다.