티스토리 뷰

반응형

작성하고 나니 여러 개념에 대해 단어들을 통일 시 못한 것 같네요. 혹시 이상한 점 있으면 편하게 피드백 부탁드립니다.

 


 

요즘 프로젝트에서 WebFlux를 적극적으로 다루고 있는데, just/defer/fromCallable를 다루다보니 조금 헷갈리는 점이 있어 정리해놓으려 한다.

모든 메서드는 Mono 를 중심으로 정리했다.

 


 

just

just는 구독 시 특정 값을 반환(emit)하는 메서드이다. 제일 기본적인 메서드이기 때문에 처음 Reactor를 공부하면 거의 제일 처음에 배웠던 것 같다.

공식문서를 보면 설명이 다음과 같다.

Create a new Mono that emits the specified item, which is captured at instantiation time.

 

여기서 instantiation time 에 초점을 맞출 필요가 있는데, 인스턴화가 되는 시간에 캡처되는 값을 반환한다고 되어있다. 조금 이해하기 어렵다면 eager 하다고 이해해도 좋을 것 같다. 이 점이 defer, fromCallable 과 다르다.

 


 

defer

defer 는 Mono supplier를 파라미터로 받아 구독 시 해당 supplier를 구독하여 반환되는 값을 전달한다.

쉽게 위 just 를 사용해 예시로 들면, just 가 반환되는 supplier를 파라미터로 넣어주면 defer 가 구독될 때 just 가 구독되면서 설정한 특정 값이 반환(emit)되는 것이다.

그럼 그냥 just 로 특정 값을 반환하는 것과 차이가 없지 않나라고 생각이 들 수 있다. 하지만 defer 의 경우, instantiation time에 캡처되지 않는다. 구독될 때, 캡처되어 반환된다. 즉, 구독을 하지 않으면 인스턴스화를 진행하지 않아 반환 값을 캡처하지 않게 된다.

 


 

fromCallable

fromCallable 은 Callable supplier를 파라미터로 받아 MonoCallable 인스턴스를 생성한다. MonoCallable 는 구독 시, Callable supplier를 통해 제공되는 값을 반환한다.

단순히 보자면 supplier를 사용한다는 점에서 defer 랑 별 차이 없어보일 수 있지만, defer 는 Mono를 제공하는 supplier를 파라미터로 받고, fromCallable 은 단순 값을 제공하는 supplier를 파라미터로 받는다.

fromCallablejust 와 차이는 deferjust 차이와 유사하다.

 


 

Example

코드를 통해 just, defer, fromCallable 의 차이를 알아보자.

int data = 99;

public int getData(String caller) {
    System.out.println("called by " + caller);
    return data;
}

@Test
void deferCallable() {

    var just = Mono.just(getData("just"));  // print `called by just`
    var defer = Mono.defer(() -> Mono.just(getData("defer")));
    var callable = Mono.fromCallable(() -> getData("callable"));

    System.out.println("Call test start");

    just.subscribe(d -> System.out.println("just d = " + d));           // print `just d = 99`
    defer.subscribe(d -> System.out.println("defer d = " + d));         // print `called by defer\n defer d = 99`
    callable.subscribe(d -> System.out.println("callable d = " + d));   // print `called by callable\n defer d = 99`

    System.out.println("data value change");
    data = 111;
    just.subscribe(d -> System.out.println("just d = " + d));           // print `just d = 99`
    defer.subscribe(d -> System.out.println("defer d = " + d));         // print `called by defer\n defer d = 111`
    callable.subscribe(d -> System.out.println("callable d = " + d));   // print `called by callable\n defer d = 111`

}

 

just 는 앞서 이야기한 것처럼 instantiation time 에 캡처된다. 즉, var just = Mono.just(getData("just")); 코드가 실행되면서 해당 Mono 시퀀스가 초기화되면서 내부 코드를 실행한다. 그래서 바로 출력이 일어나게 된다.

 

하지만 defer, callable 은 구독 시에 캡처된다. 그러므로 called by 출력문이 구독 시에 출력되게 된다. 또한, 반환되는 값에 변화를 줘도 구독 시에 Mono 시퀀스가 초기화되어 값이 반환되므로 최신 값을 반환할 수 있게 된다.

 

이는 Lazy하게 사용하고 싶을 때 잘 활용될 수 있다.

 

Mono<String> externalServiceCall() {
    return Mono.just("Response");
}

Mono<String> executeWhenEmpty() {
    System.out.println("Execute When Empty !");
    return Mono.just("Other-data");
}

@Test
void deferSwitchIfEmptyTest() {
        // (A)
    externalServiceCall()
            .switchIfEmpty(executeWhenEmpty())
            .subscribe();   // print 'Execute When Empty !'

        // (B)
    externalServiceCall()
            .switchIfEmpty(Mono.defer(this::executeWhenEmpty))
            .subscribe();   // print nothing
}

A, B 둘 다 시퀀스에 값이 있으므로 switchIfEmpty 의 시퀀스로 교체가 일어나지는 않을 것이다.

 

하지만 A의 경우는 코드가 실행되면서 executeWhenEmpty 가 캡처되어 해당 시퀀스를 실행하지도 않을 것을 모르고 초기화하게 된다. 즉, Execute When Empty ! 가 출력된다.

 

B는 다르다. 구독할 때, 시퀀스가 초기화되므로 위 예제에서는 switchIfEmpty 의 시퀀스로 수행되지 않으니 executeWhenEmpty가 실행되지 않는다.

 

만약 executeWhenEmpty 가 엄청 오래 걸리는 작업이라던지 또는 특정 공유 변수의 값을 변경하는 등의 행동을 하는 메서드였다면 어땠을까? 당연히 A처럼 하게되면 문제가 발생할 수 있다. 즉, Lazy하게 초기화할 필요가 있는 작업들은 defer 를 활용하는 편이 좋을 것이다(또는 fromCallable)

 


 

퀴즈 ~ !

그럼 이쯤에서 퀴즈를 한번 내보고자 한다.

public Mono<String> responseMonoString1() {
    System.out.println("1: This method returns some string ~!");
    return Mono.just("some");
}

public Mono<String> responseMonoString2() {
    System.out.println("2: This method returns some string ~!");
    return Mono.defer(() -> Mono.just("some"));
}

public Mono<String> responseMonoString3() {
    System.out.println("3: This method returns some string ~!");
    return Mono.fromCallable(() -> "some");
}

public String responseString() {
    System.out.println("4: This method returns some string ~!");
    return "some";
}

@Test
void deferTest() {
        // A
    responseMonoString1()
        .repeat(3)
        .subscribe(d -> System.out.println("d = " + d));

        // B
    responseMonoString2()
        .repeat(3)
        .subscribe(d -> System.out.println("d = " + d));

        // C
    responseMonoString3()
        .repeat(3)
        .subscribe(d -> System.out.println("d = " + d));

        // D
    Mono.defer(() -> responseMonoString1())
        .repeat(3)
        .subscribe(d -> System.out.println("d = " + d));

        // E
    Mono.fromCallable(() -> responseString())
        .repeat(3)
        .subscribe(d -> System.out.println("d = " + d));
}

 

deferTest 를 실행하였을 때, A/B/C/D/E에서 어떤 결과가 출력될까?

  • 참고로 repeat(3) 은 한 번의 시퀀스 실행 이후 3번을 더 반복하므로 총 4번 실행될 것이다.

 

 

결과는 다음과 같다.

# A
1: This method returns some string ~!
d = some
d = some
d = some
d = some

# B
2: This method returns some string ~!
d = some
d = some
d = some
d = some

# C
3: This method returns some string ~!
d = some
d = some
d = some
d = some

# D
1: This method returns some string ~!
d = some
1: This method returns some string ~!
d = some
1: This method returns some string ~!
d = some
1: This method returns some string ~!
d = some

# E
4: This method returns some string ~!
d = some
4: This method returns some string ~!
d = some
4: This method returns some string ~!
d = some
4: This method returns some string ~!
d = some

 

A/B/C는 구독이 되기 전에 먼저 시퀀스가 초기화 된다. Mono를 반환하고 있고 defer, fromCallable 같이 구독 시점에 초기화 되는 것처럼 구성되지 않았기에 구독되기 이전에 먼저 초기화해놓고 활용하게 되는 것이다. 그리고 구독이 일어나면서 초기화 해둔 시퀀스가 활용되고 이를 반복하면서 계속 초기화 해둔 시퀀스를 구독하게된다. 그렇기에 responseMonoString 메서드에서 출력하고 있는 문장들이 반복되어 출력되지 않는다.

 

하지만 D/E는 다르다. defer, fromCallable 을 사용하여 시퀀스를 구성해서 구독할 때 초기화되게 된다. repeat 이 실행되면서 계속 구독하게 되고 그 때마다 새롭게 메서드를 실행시켜 시퀀스를 초기화한다. 그렇기에 메서드가 계속 실행되어 특정 문장들이 반복되어 출력되게 된다.

320x100
반응형
댓글
반응형
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함