IT/Spring

Reactor flatmap vs map 비교

물통꿀꿀이 2020. 11. 28. 14:12

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