티스토리 뷰
구독자와 게시자
리액티브 프로그래밍의 핵심 구성 요소에는 구독과 게시가 있습니다.
리액티브 프로그래밍은 이벤트가 감지되면 필요한 사용자에게 전송되는 이벤트 모델을 기반으로 합니다.
UI에서 버튼을 누르는 것과 같은 이벤트를 수신하고자 할 때 정의하는 것이 구독자입니다.
반대로 이벤트를 생성하는 것이 게시자라고 생각하면 됩니다.
스프링에서는 리액터 프레임워크를 사용해서 리액티브 마이크로서비스를 만듭니다.
컨트롤러는 결과의 게시자가 될 수 있으며, 스프링은 서비스를 사용하는 모든 사용자에게 데이터를 보내기 위해 이벤트를 구독합니다. 이 데이터는 리액티브 스트림 사양에 정의된 대로 리액티브 스트림으로 전송돼 논블로킹의 배압을 제공합니다.
Mono
리액터에서는 모노라는 클래스를 통해 하나의 결과를 보내는 게시자를 정의할 수 있습니다.
Mono를 사용하려면 아래와 같은 작업을 수행하면 됩니다.
val customerMono : Mono<Customer> = Mono.just(Customer(1, "Mono"))
아래는 코틀린의 고차 함수를 사용한 것과 타입 추론을 사용한 것입니다.
val customerMono : Mono<Customer> = Customer(1, "Mono").toMono()
val customerMono = Customer(1, "Mono").toMono()
Mono는 앞으로 얻으려고 하는 데이터에 대한 약속이라고 생각하면 됩니다.
누군가가 해당 게시자를 등록하면 데이터를 얻게 됩니다.
서비스에서 Mono 사용하기
Service 인터페이스의 반환 값을 아래와 같이 바꿨습니다.
interface CustomerService {
fun getCustomer(id: Int) : Mono<Customer>?
...
}
ServiceImpl 클래스를 아래와 같이 변경하였습니다.
@Component
class CustomerServiceImpl : CustomerService {
...
override fun getCustomer(id: Int) = customers[id]?.toMono()
...
}
마지막으로 Controller를 아래와 같이 수정하였습니다.
@RestController
class CustomerController(@Autowired private val customerService: CustomerService) {
@GetMapping(value = ["/customer/{id}"])
fun getCustomer(@PathVariable id: Int) : ResponseEntity<Any> {
val customer = customerService.getCustomer(id)
return if (customer != null) ResponseEntity(customerService.getCustomer(id), HttpStatus.OK)
else ResponseEntity(ErrorResponse("404 NOT FOUND", "'$id'번째 고객 정보가 없습니다."), HttpStatus.NOT_FOUND)
}
...
}
ResponseEntity를 통해 Mono를 포함하여 반환하였습니다.
스프링은 결과를 리액티브하게 반환하기 위해 요청이 들어오면 그 상황을 이해하고 게시자를 구독합니다.
Flux로 여러 객체 게시하기
리액터는 0에서 무한대의 요소를 가진 게시자를 만들 수 있는 클래스를 제공합니다.
플럭스는 아래와 같이 사용합니다.
val customerFlux = Flux.fromIterable(listOf(Customer(1, "one"), Customer(2, "two")))
코틀린 문법을 사용해서 아래와 같이 작성할 수 있습니다.
val customerFlux = listof(Customer(1, "one"), Customer(2, "two")).toFlux()
서비스에서 Flux 사용하기
아래와 같이 코드를 수정해주었습니다.
interface CustomerService {
...
fun searchCustomer(nameFilter: String) : Flux<Customer>
}
override fun searchCustomer(nameFilter: String) =
customers.filter {
it.value.name.contains(nameFilter, true)
}.map(Map.Entry<Int, Customer>::value).toFlux()
@RestController
class CustomerController(@Autowired private val customerService: CustomerService) {
...
@GetMapping(value = ["/customers"])
fun getCustomers(@RequestParam(required = false, defaultValue = "") nameFilter: String) = customerService.searchCustomer(nameFilter)
}
플럭스도 컨트롤러가 반환하면 스프링은 자동 구성으로 새로운 요청이 들어오면 구독합니다.
리액티브하게 객체 수신
리액티브 마이크로서비스를 만들 때 스프링은 게시자를 구독하면 본문의 객체를 가지는 모노 게시자를 RequestMapping에 보낼 수 있습니다.
interface CustomerService {
...
fun createCustomer(customerMono: Mono<Customer>) : Mono<*>
}
Mono<Customer>를 받고 Mono<*>를 반환하는 createCustomer이라는 Customer를 생성하는 인터페이스를 구현하였습니다.
@Component
class CustomerServiceImpl : CustomerService {
...
override fun createCustomer(customerMono: Mono<Customer>): Mono<*> =
customerMono.subscribe {
customers[it.id] = it
}.toMono()
}
ServiceImpl에서 함수 내용을 구현하였습니다.
그리고 마지막으로 Controller에 아래와 같이 코드를 작성하였습니다.
@PostMapping(value = ["/customer"])
fun createCustomer(@RequestBody customerMono: Mono<Customer>) =
ResponseEntity(customerService.createCustomer(customerMono), HttpStatus.CREATED)
POST로 데이터를 보내면 아래의 결과를 받을 수 있습니다.
{
"disposed": false,
"scanAvailable": true
}
subscribe 함수가 Disposable 객체를 반환하고 JSON으로 직렬화되기 때문입니다. 하지만 원하는 것은 빈 데이터 일 것입니다. 빈 결과를 얻기 위해서는 아래와 같이 작성합니다.
override fun createCustomer(customerMono: Mono<Customer>): Mono<*> =
customerMono.map {
customers[it.id] = it
}
map으로 데이터를 넣어주었습니다. 따라서 빈 객체 { }를 얻게 됩니다.
만약 아래와 같이 it을 반환해주면 들어간 데이터를 반환받을 수 있습니다.
override fun createCustomer(customerMono: Mono<Customer>): Mono<*> =
customerMono.map {
customers[it.id] = it
it
}
{
"id": 5,
"name": "test",
"telephone": {
"countryCode": "+82",
"telephoneNumber": "12341234"
}
}
만약 빈 객체를 명시적으로 반환하고 싶으면 아래와 같이 작성하면 됩니다.
override fun createCustomer(customerMono: Mono<Customer>): Mono<*> =
customerMono.map {
customers[it.id] = it
Mono.empty<Any>()
}
201 CREATED와 함께 빈 객체를 결과로 반환합니다.
'알려주는 이야기 > 스프링 부트' 카테고리의 다른 글
Spring Boot - Reative Rest Api [ 오류 처리 ] (0) | 2020.09.09 |
---|---|
Spring Boot - Reative Rest Api [ 함수형 ] (0) | 2020.09.09 |
Spring Boot - Reative Rest Api [ 스프링 웹플럭스 ] (1) | 2020.09.08 |
Spring Boot - Rest Api 예외 처리 (0) | 2020.09.08 |
Spring Boot - JSON에서 Null 처리 (0) | 2020.09.08 |