Reactor flatmap vs map 비교
Webflux에서 자주 사용하는 flatmap과 map을 비교해보려고 한다.
flatmap
public final <R> Mono<R> flatMap(Function<? super T,? extends Mono<? extends R>> transformer)
실제 코드 정의는 위와 같고, Description을 보면
"Mono에 의해 비동기성으로 emit된 아이템을 다른 Mono 형태로 emit된 값으로 변형하여 반환한다." 로 되어 있다. 이해가 잘 되지 않을 수도 있으니 마블 다이어그램을 살펴보도록 하자.
subscribe가 발생하면, 기존 emit된 값이 새로운 값으로 변형되어 emit되는 것을 알 수 있다.
map
public final <R> Mono<R> map(Function<? super T,? extends R> mapper)
마찬가지로 Description을 보면
"동기 function을 적용하여 Mono에 의해 emit된 값을 변형한다."
위의 마블 다이어그램을 살펴보면 flatmap처럼 값이 변형되었다는 것을 알 수 있다.
그런데 두 메소드의 큰 차이는 비동기/동기이다.
flatmap의 마블 다이어그램을 자세히보면 subscribe 시점에 emit된 값이 변형된다. 반면에 map은 내부 function에 의해 변형된다.
return Mono.just(Person("name", "age:12"))
.map { person ->
EnhancedPerson(person, "id-set", "savedInDb")
}.flatMap { person ->
reactiveMongoDb.save(person)
}
그래서 위 코드를 읽어보면, map은 sync operation 때문에 Person이 EnhancedPerson으로 변형하고 flatmap은 값을 데이터베이스에 저장시 block 작업이 아닌 non-blocking으로 처리한다. (만약 map으로 하게 된다면, block이 되기 때문에)
만약!! nested 방식으로 쓰게 된다면??
fun makePersonASalariedEmployee(personId: String): Mono<Person> {
return personsRepository.findPerson(personId)
.flatMap { person ->
employeeService.toEmployee(person)
.flatMap { employee ->
salariedEmployeService.toSalariedEmployee(employee)
}
}
}
해당 방식은 코드를 읽기 어렵게 할 뿐 만아니라 SRP(단일 책임 원칙)을 위반한다. 때문에 nested 방식 보다는 attach 방식으로 사용하는 것을 권한다. (아래 코드 참조)
fun makePersonASalariedEmployee(personId: String): Mono<Person> {
return personsRepository.findPerson(personId)
.flatMap { person ->
employeeService.toEmployee(person)
}
.flatMap { employee ->
salariedEmployeService.toSalariedEmployee(employee)
}
}
Reference
- nikeshshetty.medium.com/5-common-mistakes-of-webflux-novices-f8eda0cd6291