여기서는 일단 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
를 사용하여 비동기 작업을 처리합니다. 컨트롤러 레이어에서는 이를 호출하여 비동기 응답을 반환합니다.- 레이어 분리: 비즈니스 로직과 비동기 처리 로직을 분리하여 코드의 가독성과 유지보수성을 높입니다.