qwerasdfzxc
165
2020-07-03 00:32:43
10
1411

spring webflux에서 flatMap과 Map의차이


@Transactional
fun saveMemo(createMemoDto: CreateMemoDto): Mono<Unit> {
return userService.getUserByUserName(createMemoDto.userName)
.map {
Memo(
createMemoDto.title,
createMemoDto.description,
createMemoDto.access,
it
)
}.flatMap { Mono.fromCallable { memoRepository.save(it) }.subscribeOn(Schedulers.elastic()) }
.map { }
}//돌아가는 코드
@Transactional
fun saveMemo(createMemoDto: CreateMemoDto): Mono<Unit> {
return userService.getUserByUserName(createMemoDto.userName)
.map {
Memo(
createMemoDto.title,
createMemoDto.description,
createMemoDto.access,
it
)
}.map { Mono.fromCallable { memoRepository.save(it) }.subscribeOn(Schedulers.elastic()) }
.map { }
}//안돌아가는 코드

webflux를 공부중인 학생입니다. 다름이 아니고 map 과 flatMap의 차이점을 몸으로 느끼지 못하겠습니다.

... 차이점을 정확하게 알지 못하다보니 그냥 함수를 때려끼우는 느낌입니다.. 도와주세요 ㅠㅠ





0
  • 답변 10

  • fx
    1k
    2020-07-03 01:00:33

    map은 펑터의 구조를 바꾸지 않습니다. 

    펑터 F에 함수 A->B 를 F에 적용하면 F<A> -> F<B> 가 됩니다. 

    펑터 F 에 함수 A-> F<B> 를 적용하면 F<A> -> F<F<B>> 가 됩니다.


    flatten 은 펑터를 평탄화 합니다. F<F<A>> 가 있으면 F<A> 로 만들어 줍니다.


    flatMap은 flatten과 map 을 수행합니다. 

    따라서 펑터 F에 함수 A -> F<B> 를 적용하면 F<A> -> F<B> 가 됩니다.


    2번째 코드에서

    .map  { Mono.fromCallable {   ...  부분이 있습니다. Mono.fromCallable 함수는 유닛 함수이고 Mono를 만들어 냅니다. 따라서 A -> Mono<B>  형식의 함수입니다.

    Mono.map 에 A -> Mono<B> 함수를 적용하므로  Mono<A> -> Mono<Mono<B>> 로 변하게 됩니다.


    .flatMap { Mono.fromCallable .... 처럼 flatMap을 사용하면  Mono<A> -> Mono<B> 가 됩니다.





  • qwerasdfzxc
    165
    2020-07-03 01:08:11

    아 이해했습니다. 감사합니다!!

  • fx
    1k
    2020-07-03 01:23:21

    뒤부분 생략된 코드가 없어서 정확하게 알 수 없지만,

    현재 보이는 부분만 보면 save 수행하는 Mono를 구독하고 있지 않습니다.

    Flux 와 Mono는 구독하지 않으면 코드를 수행하지 않습니다.


    아마도 saveMemo 반환형이 Mono<Unit> 이기 때문에 다음과 같이

    .map { Mono.fromCallable (.....).subscribeOn(...).subscribe() }

    map에서 생성한 repository  save Mono를 구독한다면 저장이 될 것입니다.


    다만 이 경우 service Mono를 구독 한 시점보다 repository Mono를 구독하는 시점은 더 늦을 것이고,

    service 와 repository가 비동기로 동작하는 점에는 주의해야 할것 같습니다.


    flatMap을 사용했을 때에는 Mono<A> -> Mono<B> 의 형태를 일관되게 유지하기 때문에

    한번의 구독으로 모두 처리할 수 있으며,  형식의 흐름이 좀 더 자연스럽다고 볼 수 있을 것 같습니다.

  • qwerasdfzxc
    165
    2020-07-03 01:44:02

    아 그렇다면 map이 동작하지않았던것은 map으로인해 mono로계속 덮이는데 어느시점에서 새롭게생긴 mono에 subscribe가 되지않아서 구동이안된것인가요?

  • fx
    1k
    2020-07-03 07:40:42 작성 2020-07-03 10:07:44 수정됨

    네, 그렇다고 볼 수 있겠습니다.

    Mono<Mono<A>> 구조이기 때문에 map을 사용할 경우 안쪽의 Mono를 구독해 주어야 합니다.


    또는 map은 A -> B로 가는것이 자연스럽기 때문에 

    .map { Mono.fromCallable (.....).subscribeOn(...).block() } 


    위와 같이 block 메소드를 사용하면 A -> M<B> 가 아닌 A -> B 가 됩니다.

    map 내부에서 사용되는 Mono는 차단되지만, 전체 코드는 비차단으로 동작합니다.



  • qwerasdfzxc
    165
    2020-07-03 23:38:01

    어제 답변해주신건 정말 너무나너무나 도움이 많이되었습니다. 감사합니다 ㅠㅠㅠ

    flatMap { memo ->
    tagService.splitTag(createMemoDto.tags)
    .map { tagString ->
    println(tagString)
    tagService.findByTagName(tagString).map { tag ->
    if (tag == null) memo.tags.add(Tag(tagString))
    else memo.tags.add(tag)
    }.subscribe()
    }.subscribe()
    Mono.fromCallable { memoRepository.save(memo) }.subscribeOn(Schedulers.elastic())
    }.map { }

    아마 비슷한 문제인 것같아서 새롭게 Mono가 생긴 것같아 구독을 해주었는데요...

    tagService.findByTagName().map{//이부분이 실행안됨} 실행이안됩니다...

    나머지는 정상 실행이되는데말이죠.. 뭐가문제일까요..?



  • fx
    1k
    2020-07-04 07:45:29

    위와 같은 경우 tagService는 memoRepository.save 보다 먼저 수행됨을 보장할 수 없게 됩니다.

    간단하게는 subscribe 대신 block를 호출하여 save 이전 작업을 차단 하는 방법이 있습니다.

    하지만 Mono의 장점은 비차단에 있으므로,  각 Mono를 flatMap으로 연결하는 것을 추천합니다.

    .flatMap { memo -> tagService ...
    .flatMap { memo -> ... { memoRepository.save(memo) ....
    .map { ...

  • qwerasdfzxc
    165
    2020-07-04 15:48:07
     tagService.findByTagName(tagString).map { tag ->
    if (tag == null) memo.tags.add(Tag(tagString))
    else memo.tags.add(tag)
    }.subscribe()

    이 구문 자체가 아예 작동을 안하는 것같습니다. println()으로 찍어보기도하고그랬는데...

    순서상관없이 일단 돌아야되는 것아닌가요??ㅠㅠ

  • qwerasdfzxc
    165
    2020-07-04 15:56:00
    tagService.findByTagName(tagString).flatMap { tag ->
    memo.save(tag)
    }.switchIfEmpty(memo.save(Tag(tagString.toUpperCase())))

    이렇게 변경했더니 정상 실행됩니다. webflux에서 null 체킹이 안되면 멈추는걸까요?


  • fx
    1k
    2020-07-05 14:06:46

    제가 각 서비스의 반환 타입을 잘 알 수 없기 때문에, 정확히 알기는 어렵습니다만,

    findByTagName 에 switchIfEmpty 를 사용해서 동작한다면,

    findByTagName이 Empty를 반환할 수도 있다는 의미 입니다.

    Mono.empty는 비어 있으므로 구독 할 수 있는 것도 없습니다.


  • 로그인을 하시면 답변을 등록할 수 있습니다.