Chatper11. Context

김신영·2023년 7월 31일
0

Spring WebFlux

목록 보기
11/13
post-thumbnail
post-custom-banner

Context란?

Context는 어떠한 상황에서 그 상황을 처리하기 위해 필요한 정보

Context 예시

  • ServletContext는 Servlet이 Servlet Container와 통신하기 위해서 필요한 정보를 제공하는 인터페이스
  • Spring Framework에서 ApplicationContext 는 애플리케이션의 정보를 제공하는 인터페이스
  • Spring Security에서 SecurityContextHolder가 관리하는, SecurityContext 는 애플리케이션 사용자의 인증 정보를 제공하는 인터페이스

Reactor에서의 Context란?

  • Operator 같은 Reactor 구성요소 간에 전파되는 Key / Value 형태의 저장소
  • Operator 체인상의 각 Operator가 해당 Context의 정보를 동일하게 이용할 수 있음
  • ThreadLocal과 다소 유사한 면이 있지만,
    • 실행 스레드와 매핑되는 것이 아니라, Subscriber와 매핑
  • 구독이 발생할 때마다 해당 구독과 연결된 하나의 Context가 생긴다.

Reactor에서는 Operator 체인상의 서로 다른 Thread들이 Context의 저장된 데이터에 손쉽게 접근할 수 있다.

코드 예시

Mono
    .deferContextual(ctx ->
        Mono
            .just("Hello" + " " + ctx.get("firstName"))
            .doOnNext(data -> log.info("# just doOnNext : {}", data))
    )
    .subscribeOn(Schedulers.boundedElastic())
    .publishOn(Schedulers.parallel())
    .transformDeferredContextual(
            (mono, ctx) -> mono.map(data -> data + " " + ctx.get("lastName"))
    )
    .contextWrite(context -> context.put("lastName", "Jobs"))
    .contextWrite(context -> context.put("firstName", "Steve"))
    .subscribe(data -> log.info("# onNext: {}", data));

Thread.sleep(100L);

/*
19:58:52.925 [boundedElastic-1] INFO - # just doOnNext : Hello Steve
19:58:52.934 [parallel-1] INFO - # onNext: Hello Steve Jobs
*/
  • deferContextual()
    • Context에 저장된 데이터와 원본 데이터 소스의 처리를 지연시키는 역할
    • 원본 데이터 소스 레벨에서 Contxt의 데이터를 읽기
    • 파라미터로 정의된 람다 표현식의 람파 파라미터는 ContextView 타입의 객체
  • transformDeferredContextual() Operator를 사용해서 Operator 체인 중간에서 데이터를 읽을 수 있다.
  • contextWrite() Operator를 사용해서 Context에 데이터 쓰기 작업을 할 수 있다.

ContextView

  • Context에 저장된 데이터를 읽을 때만 사용
  • context.readOnly()

Context의 특징

  • 구독이 발생할 때마다 해당하는 하나의 Context가 하나의 구독에 연결된다.
    final String key1 = "company";
    
    Mono<String> mono = Mono.deferContextual(ctx ->
                    Mono.just("Company: " + " " + ctx.get(key1))
            )
            .publishOn(Schedulers.parallel());
    
    mono.contextWrite(context -> context.put(key1, "Apple"))
            .subscribe(data -> log.info("# subscribe1 onNext: {}", data));
    
    mono.contextWrite(context -> context.put(key1, "Microsoft"))
            .subscribe(data -> log.info("# subscribe2 onNext: {}", data));
    
    Thread.sleep(100L);
    
    /*
    20:23:45.821 [parallel-2] INFO - # subscribe2 onNext: Company:  Microsoft
    20:23:45.821 [parallel-1] INFO - # subscribe1 onNext: Company:  Apple
    */
  • Context는 Operator 체인 아래에서 위로 전파된다.
  • 동일한 키에 대한 값을 중복해서 저장하면, Operator 체인에서 가장 위쪽에 위치한 contextWrtie() Operator가 저장한 값으로 덮어 쓴다.
  • ✅ contextWrite() 는 Operator 체인의 맨 마지막에 둡니다!
    String key1 = "company";
    String key2 = "name";
    
    Mono
        .deferContextual(ctx ->
            Mono.just(ctx.get(key1))
        )
        .publishOn(Schedulers.parallel())
        .contextWrite(context -> context.put(key2, "Bill"))
        .transformDeferredContextual((mono, ctx) ->
                mono.map(data -> data + ", " + ctx.getOrDefault(key2, "Steve"))
        )
        .contextWrite(context -> context.put(key1, "Apple"))
        .subscribe(data -> log.info("# onNext: {}", data));
    
    Thread.sleep(100L);
    
    /*
    20:29:50.870 [parallel-1] INFO - # onNext: Apple, Steve
    */
  • ✅ Inner Sequence 내부에서는 외부 Context에 저장된 데이터를 읽을 수 있다.
  • ❌ Inner Sequence 외부에서는 Inner Sequence 내부 Context에 저장된 데이터를 읽을 수 없다.
    String key1 = "company";
    Mono
        .just("Steve")
    //    .transformDeferredContextual((stringMono, ctx) -> 
    //            ctx.get("role")) // 주석 제거시, 에러 발생
        .flatMap(name ->
            Mono.deferContextual(ctx ->
                Mono
                    .just(ctx.get(key1) + ", " + name)
                    .transformDeferredContextual((mono, innerCtx) ->
                            mono.map(data -> data + ", " + innerCtx.get("role"))
                    )
                    .contextWrite(context -> context.put("role", "CEO"))
            )
        )
        .publishOn(Schedulers.parallel())
        .contextWrite(context -> context.put(key1, "Apple"))
        .subscribe(data -> log.info("# onNext: {}", data));
    
    Thread.sleep(100L);
    
    /*
    20:55:24.846 [parallel-1] INFO - # onNext: Apple, Steve, CEO
    */
profile
Hello velog!
post-custom-banner

0개의 댓글