yangbongsoo / tistory-comments

0 stars 0 forks source link

84 #9

Open utterances-bot opened 2 years ago

utterances-bot commented 2 years ago

WebClient 사용할때 주의 (7편)

webClient 사용할 때 주의사항까진 아니지만 리스트 형태의 response body 를 받는 방법에 대해서 간단히 정리했다. 가끔 Mono 형태로 리턴 webClient 코드를 볼 때가 있다. flatMapIterable 메서드로 Flux 를 사용하는게 좋다. public Flux getData() { return this.webClient.get() .uri(uriBuilder -> uriBuilder .path("/data") .build() ) .retrieve() .bodyToMono(DataApiResponse.class) .flatMapIterable(DataApiResponse::getContent); } @Value public class DataApiResponse

https://yangbongsoo.tistory.com/84

honghyunin commented 2 years ago

깔끔하게 정리된 글 잘 읽었습니다! 실례지만 한 가지 도움을 받을 수 있을 까 코멘트 남깁니다😭

코틀린 환경에서 아래의 json을 Flux<String>의 반환 형태로

[
    "KR_6194794594",
    "KR_6189780882",
    "KR_6188567011"
]

전자의 경우 String이 ["KR_6194794594","KR_6189780882","KR_6188567011"] 다음과 같이 하나의 인덱스에 디코딩 되어 나오게 되더라구요.. 후자의 경우 아에 역직렬화 하는 과정에서 MismatchedInputException 예외가 발생하였구요...

혹시 해결 방법을 아신다면 알려주시면 감사하겠습니다 ㅠㅠ..

yangbongsoo commented 2 years ago

안녕하세요 @honghyunin 님 :)

먼저 아래 형태는 json 이 아닙니다. json 은 key, value 형태여야 하며 중괄호가 있어야 합니다.

[
    "KR_6194794594",
    "KR_6189780882",
    "KR_6188567011"
]

그리고 bodyToFlux operation 으로 역직렬화를 시도했다고 말씀주셨는데 코드도 같이 보여주시면 좋겠습니다.

전자의 경우 String이 ["KR_6194794594","KR_6189780882","KR_6188567011"] 다음과 같이 하나의 인덱스에 디코딩 되어 나오게 되더라구요..

마지막으로 위 문장에 쓰인 워딩들이 이해를 어렵게 합니다. 하나의 인덱스에 디코딩 되어 나온다 고 표현하셨는데 여기서 말하는 인덱스는 무엇일까요? 그리고 디코딩되어 나온다고 하신것도 어떤 디코딩인가요?

["KR_6194794594","KR_6189780882","KR_6188567011"] 는 대괄호로 표현된 리스트인데 [, ] 포함해서 하나의 문자열로 담겼다는 뜻인가요?

honghyunin commented 2 years ago

빠르게 답변해주셨는데 제가 답변이 늦어졌네요 ㅠㅠ

그리고 bodyToFlux operation 으로 역직렬화를 시도했다고 말씀주셨는데 코드도 같이 보여주시면 좋겠습니다.

코드는 아래의 두 가지 방법을 사용했습니다

override fun getMatchIds(puuid: String, matchPageable: MatchPageable): Flux<String> {
        return webClient.mutate().build()
            .get().uri(riotProperties.matchUUIDUrl + puuid + "/ids?queue=" + matchPageable.queue + "&count=" + matchPageable.count)
            .retrieve()
            .bodyToFlux(String().javaClass)
    }
override fun getMatchIds(puuid: String, matchPageable: MatchPageable): Flux<String> {
        return webClient.mutate().build()
            .get().uri(riotProperties.matchUUIDUrl + puuid + "/ids?queue=" + matchPageable.queue + "&count=" + matchPageable.count)
            .retrieve()
            .bodyToMono(MatchIdResponse().javaClass)
            .flatMapIterable(MatchIdResponse::matchIds)
    }
data class MatchIdResponse(
    val matchIds: MutableList<String> = mutableListOf(),
)

(두 번째 방법의 Dto)

첫 번째 방법을 사용했을 때 [, ] 포함해서 하나의 문자열로 담겼고, 두 번째 방법을 사용했을 때에는 MismatchedInputException 예외가 발생하였습니다.

하나의 인덱스에 디코딩 되어 나온다 고 표현하셨는데 여기서 말하는 인덱스는 무엇일까요? 그리고 디코딩되어 나온다고 하신것도 어떤 디코딩인가요?

제가 이해하기에 Flux는 여러 개의 요청 값을 받을 수 있는 타입이기에 내부에 여러 개의 요소들이 있다고 생각하여 인덱스란 표현을 사용하였습니다! 마땅히 적절하진 않았나 보네요

["KR_6194794594","KR_6189780882","KR_6188567011"] 는 대괄호로 표현된 리스트인데 [, ] 포함해서 하나의 문자열로 담겼다는 뜻인가요?

네 그렇습니다. 외부 API에서 내려주는 값이 List 이지만, 디버깅 했을 때 한 각 인덱스에 문자열 값이 들어있는 것이 아닌, 한 문자열 안에 모든 값이 존재하였습니다.

[
    "KR_6194794594",
    "KR_6189780882",
    "KR_6188567011"
]
honghyunin commented 2 years ago

이런저런 말을 쓰다보니 너무 길어졌네요 요약하자면

[
    "KR_6194794594",
    "KR_6189780882",
    "KR_6188567011"
]

이 값을 Flux으로 받고 싶은데 현재 Flux을 subscribe로 값에 접근해보면 하나의 문자열에 ["KR_6194794594","KR_6189780882","KR_6188567011"] 다음과 같이 담겨있어 List의 형태가 아닌 단일 String의 형태를 띄고 있었습니다

결론은 외부 API에서 내려준 값을 Flux으로 변환하여 LIst 처럼 사용하고 싶습니다.

block을 통해 dto로 값을 받을 때에는 이 LIst 변환이 잘 이루어졌는데 Flux 상황에서 문자열이 이렇게 나와 어떻게 해야 할지를 모르겠네요..

아래 링크는 Flux 관련한 이슈입니다 https://github.com/reactor/reactor-netty/issues/589

yangbongsoo commented 2 years ago

@honghyunin 님 문제 범위를 좁혀서, 아래와 같이 간단한 컨트롤러 메서드와 테스트코드를 코틀린으로 만든다음에 확인해보시겠어요?

@RestController
public class TestController17 {

    @GetMapping("/test17")
    public TestDto test17() {
        return TestDto.builder()
            .list(List.of("1", "2", "3"))
            .build();
    }
}
@Value
@Builder
public class TestDto {
    List<String> list;
}
    @Test
    void fluxTest() {
        Flux<String> flux = webClient
            .get()
            .uri("/test17")
            .retrieve()
            .bodyToMono(TestDto.class)
            .flatMapIterable(TestDto::getList);

        List<String> collect = flux.toStream().collect(Collectors.toList());
        System.out.println(collect); // [1, 2, 3]
    }
honghyunin commented 2 years ago

@yangbongsoo 님이 작성해주신 테스트를 실행해보니 정상적으로 LIst이 출력됩니다.

아마도 외부 API에서 내려주는 List가 문제인 것 같아 API에서 내려주는 하나의 문자열을 JSONParser를 통해 List으로 변환하여 해결하였습니다

부족한 질문에 한땀한땀 답변해주셔서 감사합니다!!!! @yangbongsoo 님 덕분에 정확한 원인을 파악해서 문제를 해결할 수 있었습니다!!!!!!

yoo0926 commented 2 years ago

안녕하세요~ 포스팅 잘 읽었습니다. 관련 지식이 아직 부족한거 같아서 계속 공부중인데 읽다보니 궁금한게 생겨서 코멘트 남깁니다.

Mono<List>로 리턴을 받는거보단 Flux를 사용하는게 좋다는게 어떤 성능상 이점이 있어서 권장하는걸까요? webClient의 response뿐만 아니라 대부분의 경우 리턴타입이 Mono<Lists>인 경우 Flux로 받는게 맞을까요?

Flux가 0~N 이니까 맞는건가 싶으면서도 제가 Flux를 사용할 땐 여러개의 Mono를 받아서 신호를 흘려보내면서 각각 처리를 하고 N개의 결과를 내기위해 사용했는데 단순히 List를 Flux로 사용하기 위한거라면 이미 List가 메모리에 올라가 있는데 뭔가 불필요한 단계가 추가되는건가 싶기도 해서요.

yangbongsoo commented 2 years ago

@yoo0926 님 https://www.baeldung.com/java-reactor-flux-vs-mono 에 잘 정리되어 있어서 가져왔습니다.

Mono and Flux are both implementations of the Publisher interface. In simple terms, we can say that when we're doing something like a computation or making a request to a database or an external service, and expecting a maximum of one result, then we should use Mono.

When we're expecting multiple results from our computation, database, or external service call, then we should use Flux.

리액티브 패러다임에 맞춰진 reactor-core 스펙에 따라 Mono, Flux 를 선택했다고 봐주시면 좋을거 같습니다.

Mono<List>로 리턴을 받는거보단 Flux를 사용하는게 좋다는게 어떤 성능상 이점이 있어서 권장하는걸까요? 질문에 대한 답은 어렵네요 :) 성능상 이점을 따지려면 좀 더 구체적인 정보가 필요합니다. 예를들어 List 원소가 백단위 or 천단위 라면 성능을 따지는게 크게 의미가 없겠죠. List 원소가 몇백만개라고 했을 때는 parallel stream 을 사용하거나 List 조회 자체를 쪼개거나 하는 등 여러 방식으로 효율화를 생각해볼 수 있을거 같습니다. 다시말해 구체적인 상황 설명과 코드가 없는 상태에서 성능상 이점을 얘기하기는 어렵네요.

cf) reactor-core 를 만든 개발자들은 성능 관련 벤치마크 테스트 등 관련 정보를 알 수 있다고 생각합니다만 Mono<List> 와 Flux 차이를 deep 하게 분석할만한 가치가 있는지는 잘 모르겠습니다.

yangbongsoo commented 2 years ago

하지만 @yoo0926 님이 달아주신 댓글은 재밌었습니다 ㅎㅎ 당연하게만 받아들였던 Mono Flux 에 대해 다시 생각해봤습니다. 나중에라도 더 deep 한 내용을 알게 되면 공유 부탁드려요 :)

edgarSang commented 1 year ago

안녕하세요 글 잘봤습니다! 궁금한게 있습니다. 만약 List형태가 아니고 단일 객체를 webclient 응답값으로 받거나, Map 객체를 받았을때 사용하는 소스코드단에서 어떻게 Mono를 풀어쓸수있나요? 위처럼 그냥 Flux로 받아서 사용단에서 stream으로 바꿔서 Aclass를 받아주면 되는걸까요? 현재는

 Mono<A> aVarMono = getWebclientResponse();
 aVarMono.flatmap(o -> o) ....

이런식으로만 쓰고있는데 위 Flux의 예시처럼 reactor 객체를 한꺼풀 쉽게 벗길수 있는 방법이 있는지 궁금합니다.

yangbongsoo commented 1 year ago

@edgarSang 님 Mono 에서는 block 이나 blockOptional 을 쓰시면 됩니다.