본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


Spring framework/Spring Webflux

spring webflux 2 (웹플럭스 적용기, 기본 구조)

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2020. 3. 11.

 

웹플럭스에 대한 자료를 구글링해보면 영어로된 원문이 참 많다.

그런데 아직 한글로 제대로된 튜토리얼은 안보인다..흠..

웹플럭스라는 프레임워크는 반응형+함수형 프로그래밍을 기초로 두고 있어서 아직까지는 보편화되어 많이 사용되지 않는 것 같다.

 

 

* 반응형에 대한 간단한 정의 ─

 

반응형에 대한 내용을 간단히 정리해보자.

기존에 대부분의 프로그래밍은 아래 사진처럼 명령형으로 되어있다.

프로그래머가 만든 코드를 순서대로 실행하는 것이 기본 베이스였다.

당연한 그림, C는 변하지 않지..

 

위 내용을 자바스크립트로 표현하면 아래처럼 나타낼 수 있다.

var numberA = 2;
var numberB = 2;
var resultC = 0;
var func = (a,b)=>{resultC = a*b};

func(numberA, numberB);  //이때 resultC의 값은 4 이다.
console.log(resultC);

//numberA와 numberB가 데이터가 바뀌었다.
numberA = 4;
numberB = 4;

console.log(resultC);  //이때 resultC의 값은 당연히 4 이다.

 

따라서 데이터가 변하더라도 따로 프로그래밍을 해 주지 않는 한 resultC의 값은 여전히 그대로를 유지한다.

왜냐하면 따로 곱하기 기능을 가지고 있는 func를 호출하지 않았기 때문이다.

그런데, 반응형 프로그래밍은 이와는 조금 다르다.

번호 순서대로 살펴보아요~

 

반응형 프로그래밍에서의 가장 중요한 내용은 바로 "구독"이다.

위 사진은 숫자A와 숫자B를 바라보게 하고 해당 데이터를 곱하기 함수에게 "구독"하게 하였다.

곱하기 함수는 숫자A와 숫자B를 바라보면서 데이터의 흐름과 변동이 있을 때 마다 곱하기 동작을 하게 하였다.

그러므로 구독을 하는 행위를 종료하지 않는다면 곱하기 함수는 두개의 데이터의 변화가 있을 때 마다 곱하기를 수행하여 결과를 넣어 줄 것 이다.

 

이걸 신문에 비유하여보면,

1. 오늘 나는 신문을 보겠다고 신문사A에 돈을 지불하고 아침마다 신문을 받게 하였다.

  -> 곱하기 함수에게 숫자A와 숫자B를 구독하게 한다.

2. 아침에 신문이 배달되어 나는 신문을 읽는다.

  -> 데이터가(숫자A, 숫자B) 변동이 된다.   (배달되어~)

  -> 곱하기 함수에 정의된 곱하기 연산이 동작하여 결과 값을 결과C에 넣어준다.  (읽는다~)

3. 해당 신문사A에 돈을 주지 않을 때 까지 신문은 계속 배달되며, 나는 신문을 계속 읽는다.

  -> 따로 종료하지 않는 한 2번행위가 반복된다.

 

반응형은 위와 같은 내용으로 동작을 한다.

웹플럭스는 이러한 반응형 기법으로 구성된 프레임워크이며 "구독"을 통한 행위가 대부분이라 볼 수 있다.

 

 

* 다시 웹플럭스로 ─

 

첫번째장에서 내용을 토대로 동작원리를 파해쳐보자.

 

처음에 만들었던 FirstHandler 클래스의 hello메소드는 Mono<T>라는 클래스를 리턴해 주고 있다.

해당 클래스를 사용하는 RoutsConfig클래스에서는 이중콜론연산자(::) 를 활용해서 해당 메소드를 사용했다.

백문이 불여일견!

앞에서 만들었던 FirstHandler 클래스를 다시 살펴보자.

//import 생략

@Component
public class FirstHandler {
	
    private HashMap<Object, Object> result = new HashMap<>();
    private Mono<HashMap<Object, Object>> mapper = Mono.just(result);
	
    public Mono<ServerResponse> hello(ServerRequest request) {
        result.put("number", 1234);
        result.put("text", "webFlux");			
        mapper.subscribe( (arg)->{
            System.out.println(arg);
        });
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromProducer(mapper, HashMap.class));
    }
}

 

요렇게 생겼었는데.. hello메소드에서 기록한 내용이 RoutsConfig클래스에서 사용자의 요청에 대해서 응답했었다.

위 클래스를 만들어준 이유는 코드를 분리화 하기 위해서였다.

RoutsConfig클래스를 만약에 FirstHandler 클래스없이 사용하려면 아래처럼 코드를 바꾸어야한다.

//..import 생략
@Configuration
public class RoutsConfig implements ApplicationContextAware, WebFluxConfigurer {

    ApplicationContext context;

/*
 * 기존 코드
    @Bean
    public RouterFunction<ServerResponse> route(FirstHandler handler) {
        return RouterFunctions.route(
            RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), handler::hello);
    }
*/

    //변경 후 코드
    @Bean
    public RouterFunction<ServerResponse> route(FirstHandler handler) {
        return RouterFunctions.route(
            RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), arg->{
            HashMap<Object, Object> result = new HashMap<Object, Object>();
            result.put("number", 1234);
            result.put("text", "webFlux");	
            Mono<HashMap<Object, Object>> mapper = Mono.just(result);
            Mono<ServerResponse> response = ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromProducer(mapper, HashMap.class));
            return response;
        });
    }
    
    //..하단부 생략
}
	

 

딱 봐도 무거워진 느낌이 든다.

그래서 FirstHandler 클래스를 만들어 코드를 분리하고 재사용 가능하도록 한 것 이다.

 

자세히 보면 RoutsConfig 클래스에서 route의 메소드와 index 메소드는 둘다 Bean 객체이면서 RouterFunction 클래스를 리턴한다.

모든 메소드의 역할을 알 수는 없지만 결국 사용자에게 전달하는 데이터, 화면 등 이러한 모든 내용은 결국에는 RouterFunction라는 클래스가 있어야 하는 것 으로 보인다.

 

 

* 웹플럭스의 핵심 RouterFunction 인터페이스

 

웹플럭스에서는 RouterFunction 라는 클래스를 활용해서 사용자의 요청에 대한 응답 행위를 정의하도록 한다.

즉, 사용자는 RouterFunction 클래스만 채워주면 기타 동작은 전부 웹플럭스가 대신 해 주는 것 이다.

 

라우터펑션의 모습

 

RouterFunction은 인터페이스로 이루어져 있으며 인터페이스 내부의 route 라는 메소드는 HandlerFunction을 사용하는 Mono라는 클래스를 되돌려 주게 되어있다.

 

핸들러펑션의 모습

 

 

역시...말이 너무 어렵다..

아주 단순하게 생각하면, RouterFunction은 사용자의 요청에 대한 행위를 의미하며, 그 행위를 만드는 것은 HandlerFunction 형태로 되어있는 Mono클래스라고 보면 된다.  

 

즉, 웹플럭스를 통해서 사용자에게 일반 화면을 보여주거나, 단순 Json형식으로 데이터를 보내주는 등 이러한 대부분 행위는 사용자가 정의한 RouterFunction 클래스 내부에 기록된 함수형태를 따르게 된 다는 것 이다.

 

스프링(Spring Frameworkd)에 비유하자면,

Controller의 역할을 Bean 객체로 등록된 메소드가 담당을 하며, 해당 메소드는 RouterFunction을 리턴하도록 해야 되는 것이라고 생각하면 될 것 같다.

  -> 의미가 이상하면 말씀 부탁드립니다!!!

 

 

 

* 웹플럭스의 세부행동은 Mono 클래스가 담당!

 

HandlerFunction 인터페이스의 내부에 보여진 Mono클래스의 설명과 사용법은 다음과 같다.

https://tech.kakao.com/2018/05/29/reactor-programming/

 

점입가경이라는 말이 딱 나온다.. 뭔 말인지 점점 더 어렵다.

Mono라는 녀석의 핵심은 "행동(구독)" 이다.

사용자는 데이터나 행위에 대해서 행동(구독)을 정의하고나면 그에 따른 실행은 Mono가 알아서 하는 것 이다.

 

위 내용이 어렵다면 아래 샘플로 만든 JustTest클래스를 구동하여보자.

import java.util.concurrent.CompletableFuture;
import reactor.core.publisher.Mono;

public class JustTest {

    public static void main(String[] args) {
        //1번내용 ----------------
        String text = "abcd";
        Mono<String> mono = Mono.just(text);		
        mono.subscribe( str->{  //변수 text를 살펴보고, 그에대한 콜백행위는 아래의 출력
            System.out.println(str);
        });

        //2번내용 ----------------
        Mono<Object> function = Mono.create( sink->{  //마치 자바스크립트의 Promise 같은 느낌!
            //sink.error(new Exception("익셉션"));    -> 에러를 발생시킨다.
            sink.success("success");   //-> success값을 전달하여준다.
        });
        function.subscribe( arg->{  //위에서 전달한 success값
            System.out.println(arg);
        });
		
        //3번내용 ----------------		
        mono.doOnNext(str->{  //맨 처음 구독한 다음행위에 대한 지정
            System.out.println("next : "+str);
        }).subscribe();
		
		
        //4번내용 ----------------		        
        CompletableFuture<?> future = CompletableFuture.supplyAsync(()->{  
            System.out.println("run!");
            return "PARAM";
        });
        Mono<Object> mono2 = Mono.fromFuture(future); //비동기에 대한 구독!
        mono2.subscribe( (param)->{
            System.out.println("param : " + param);
        });
    }
}

 

 

요렇게 1차로 정리하면,

1. RouterFunction 클래스는 사용자의 요청에 대한 행동(데이터 전달, 데이터 처리, 화면 전달 등)을 정의한다.

2. RouterFunction 클래스를 빈 객체로 등록하여 웹플럭스에게 맞기면 RouterFunction클래스에 정의된 내용을 실행하여 준다.

3. RouterFunction 클래스는 HandlerFunction라는 인터페이스를 활용하여 행위를 받게 되어있고, HandlerFunction 인터페이스는 Mono라는 클래스의 내용으로 구성되어 있다.

 

 

으앜...역시나 어렵다.

다음번에는 위 내용을 토대로 샘플코드를 좀 만들면서 거꾸로 이해하도록 해 보아야겠다.

spring webflux!

 

example.zip
0.06MB

 

* 악성코드나 바이러스 없습니다!!! >ㅁ<

* 내용을 채우고 수정중입니다.

* 튜토리얼이나 가이드 목적보다도 개념정리에 목적을 두고 쓰고있습니다. 틀린부분이나 누락된 부분은 꼭 알려주세요!

 

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글