본문 바로가기
백엔드/스프링+boot

WEBFLUX

by 임지혁코딩 2024. 2. 18.

프로젝트에서 결제요청(정책상 문제로, 사용 카드 정보를 개인이 저장할 수 없어 open api를 호출해야만 한다) 

에 대한 문제로 인하여 ,  webflux를 자주 사용하게 되었다.

 

그 과정에서 모르는 내용이 계속 등장하여, 이렇게 정리하게 되었다.

 

WEBCLINET는, REST API를 위한 것으로 , 마치 REQUEST를 보내는 CLIENT처럼 동작할 수 있다.

 

<생성>

1. 간단한 생성

webclient.create(String baseurl)로 생성할 수 있다. 

 

2. 기타 다른 정보를 추가하려면, builder.build를 사용하자

private final WebClient webClient = WebClient.builder().baseUrl("https://api.portone.io").build();

 

1) header의 설정, cookie의 설정

2) request,response중 어떤것인지에 대한 설정

등이 가능하다. 

 

webClient.get().uri("/payments/{paymentId}", validation.getPaymentId())
        .header("Authorization", "Bearer " + mytoken)

 

<호출 결과>

.retrieve()를 통해, 응답을 받아올 수 있다. 

 

.bodytomono를 통해, 받아온 응답을 원하는 객체에 매핑시킬 수 있다. 

 

webClient.get().uri("/payments/{paymentId}", validation.getPaymentId())
        .header("Authorization", "Bearer " + mytoken)
        .retrieve()
        .bodyToMono(PurChaseCheck.class)

 

#만약 post라면,

.bodyValue(원하는인스턴스)를 통해 전달이 가능하다.

 

<동기, 비동기>

동기적으로 받아올때는 .block()을 사용한다.

 

허나, 만약 이미 비동기적으로 전송하는 것이 약속이 되어있거나,

api 호출 -> 작업 -> 다른 api 호출과 같은 방식에서는

일관성이 필요하다.

 

PurChaseCheck purChaseCheck = webClient.get().uri("/payments/{paymentId}", validation.getPaymentId())
        .header("Authorization", "Bearer " + mytoken)
        .retrieve()
        .bodyToMono(PurChaseCheck.class)
        .block();

.block으로 받아오면, 그 객체를 마음껏 사용할 수 있다. 

 

PurChaseCheck purChaseCheck = webClient.get().uri("/payments/{paymentId}", validation.getPaymentId())
        .header("Authorization", "Bearer " + mytoken)
        .retrieve()
        .bodyToMono(PurChaseCheck.class)
        .subscribe(purchasecheckresponsewebclient -> {

        });

허나 이렇게 subscribe로 객체를 받아오면, 문제가 생긴다. 

 

그 이유는, subscribe는 사실 작업을 완료한 이후에 진행할 callback을 등록하는 것이기 때문이다.

즉 위 코드에서 작업이 완료될때까지 기다리지 않고, 계속해서 코드를 진행시킨다. 

그렇기 때문에 진행이 완료되지 않은 purchasecheckresponsewebclinet를 그대로 사용할 수 없다. 

 

import java.util.Map;

@RestController
@Slf4j
public class PaymentController {

    private final WebClient webClient = WebClient.builder().baseUrl("https://api.portone.io").build();

    private final String mytoken = "eyJraWQiOiI2YmZhMWMzYy02N2JjLTQ2N2YtYjdlYy01ODc4M2YwYjc3MWMiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlci01NTk3NjQ1Mi0zNjQ5LTQyYTMtYjY0MC1iMTEyNGM5MDQ0ZTQiLCJpc3MiOiJJQU1QT1JUIiwibWVyY2hhbnRfc2VydmljZSI6eyJpbmNsdWRlX3Blcm1pc3Npb25zIjp0cnVlLCJtZXJjaGFudF9pZCI6Im1lcmNoYW50LTk1NWQ0MzZjLWFlOTgtNGNjYS1iNWQ2LWFhMWY2NzA3YzZmMCIsInN0b3JlX2lkIjoic3RvcmUtOGMxNDNkMTktMmU2Yy00MWUwLTg5OWQtOGMzZDAyMTE4ZDQxIiwiYmVsb25nX3RvIjoiTUVSQ0hBTlQiLCJwZXJtaXNzaW9ucyI6WyJIT01FX0FORF9SRVBPUlQiLCJQR19BUFBMSUNBVElPTl9SRUFEIiwiUEdfQVBQTElDQVRJT05fVVBEQVRFIiwiVFhfUkVBRCIsIlRYX1VQREFURSIsIkNIQU5ORUxfUkVBRCIsIkNIQU5ORUxfVVBEQVRFIiwiU1RPUkVfUkVBRCIsIlNUT1JFX1VQREFURSIsIk1FUkNIQU5UX1JFQUQiLCJNRVJDSEFOVF9VUERBVEUiLCJBUElfS0VZX1JFQUQiLCJBUElfS0VZX1VQREFURSIsIlVTRVJfUkVBRCIsIlVTRVJfVVBEQVRFIiwiU1RPUkVfU0VUVElOR19SRUFEIiwiU1RPUkVfU0VUVElOR19VUERBVEUiXSwid2hpdGVsaXN0IjpbXX0sImN1c3RvbV9wYXlsb2FkIjp7fSwiZXhwIjoxNzA4MjY1MzAzLCJpYXQiOjE3MDgyNjM1MDN9.7GoWHhoOoR4k9r0VfT4TtyCBlmx_hEOlj09WqfqwCqwk5vsfraAmwZ9IvXwfyXSLXuA51G7Qdbo2iwJxEgjKUQ";

    @PostMapping("/payments/complete")
    public PurChaseCheck completePayment(@RequestBody ValidationRequest validation) {
        log.info("CALL OK");

        webClient.get().uri("/payments/{paymentId}", validation.getPaymentId())
                .header("Authorization", "Bearer " + mytoken)
                .retrieve()
                .bodyToMono(PurChaseCheck.class)
                .subscribe(purchasecheckresponsewebclient -> {

                    if (purchasecheckresponsewebclient.getAmount().equals(300))
                    {
                        return purchasecheckresponsewebclient ;

                    };

                });
                
        return null ;        
    }
}

 

이러한 식의 진행도 불가능하다. 

 

왜냐하면 비동기적 진행이며, 이 진행중 멈춰주지 않기 떄문에 비동기의 subscribe안에서 return이 불가능하기 때문이다. 

 

그렇다면, 이를 그대로 보내고 싶다면 method 자체의 return을 mono에 감싸야한다! 

 

@PostMapping("/payments/complete")
public Mono<PurChaseCheck> completePayment(@RequestBody ValidationRequest validation) {
    log.info("CALL OK");

    return webClient.get().uri("/payments/{paymentId}", validation.getPaymentId())
            .retrieve()
            .bodyToMono(PurChaseCheck.class)
            .flatMap(purchaseCheckResponseWebClient -> {
                if (purchaseCheckResponseWebClient.getAmount().equals(300)) {
                    return Mono.just(purchaseCheckResponseWebClient);
                } else {
                    return Mono.empty();
                }
            });
}

 

#mono를 그대로 return해서는 안된다면?

 

@PostMapping("/payments/complete")
public Mono<ResponseEntity<PurChaseCheck>> completePayment(@RequestBody ValidationRequest validation) {

    Mono<PurChaseCheck> monoResult = webClient
            .get()
            .uri("/payments/{paymentId}", validation.getPaymentId())
            .header("Authorization", "Bearer " + mytoken)
            .retrieve()
            .bodyToMono(PurChaseCheck.class);

    return monoResult.map(purchasecheckresponsewebclient -> {
        if (purchasecheckresponsewebclient.getAmount().equals(300)) {
            //여기서검증을더해야됩니다
            return ResponseEntity.ok(purchasecheckresponsewebclient);
        } else {
            //안될시 응답도 정해야됩니다.
            return ResponseEntity.badRequest().build();
        }
    });
}

mono를 이용해서 정보를 받아 온후에, 해당 mono의 값을 받아와서
responseentity의 형태로 다시 전달한다. 

 

@PostMapping("/payments/complete")
public Mono<ResponseEntity<PurChaseCheck>> completePayment(@RequestBody ValidationRequest validation) {

    Mono<PurChaseCheck> monoResult = webClient
            .get()
            .uri("/payments/{paymentId}", validation.getPaymentId())
            .header("Authorization", "Bearer " + mytoken)
            .retrieve()
            .bodyToMono(PurChaseCheck.class);

    return monoResult.map(purchasecheckresponsewebclient -> {
        if (purchasecheckresponsewebclient.getAmount().getTotal() == 300) {
            log.info("verygood");
            log.info(String.valueOf(purchasecheckresponsewebclient.getAmount())) ;

            //여기서검증을더해야됩니다
            //현재 여기서 걸림.
            return ResponseEntity.ok(purchasecheckresponsewebclient);
                      }
        else {
            //안될시 응답도 정해야됩니다.
            log.info("problemoccur");
            return ResponseEntity.badRequest().build();


        }
    });
}

 

최종적으로 구현할 수 있는 형태. 

 

mono로, responseentity로, 원하는 형태를 감싼다. 

감싼형태에서 다시 mapping하여 원하는 추가 행위를 진행한다. 

 

 

 

'백엔드 > 스프링+boot' 카테고리의 다른 글

MAIL로 첨부파일 JPG를 보내는 과정에서의 문제들  (0) 2024.05.16
Spring에서 동시성 test  (0) 2024.03.29
기타 기술 + JPA 기본 설정  (0) 2024.01.04
VIew, View template  (1) 2024.01.04
오류  (4) 2024.01.04