그동안 스프링 웹플럭스를 사용하면서 나름대로 이게 왜 사용되고 어떤 방식으로 동작되는지에 대해서 누구도 간결하게 설명해주지 않아서 한번 정리해보는 글. (개념이 확실히 정리되지 않으면 설명이 장황해지는 면이 있는데, 그래서인지 뭔가 특히 이 분야에서 공부를 하면서 왜 이렇게 장황하게 설명할까? 싶은 개념들이 많았다. 요걸 내 식대로 해석해보는건데.. 사실 내 해석이 틀렸을 수도 있고.. 그런 부분은 댓글 부탁드립니당 :-) )
비동기-논블락킹, 그리고 이벤트 드리븐이라는 두 가지 단어와 함께 설명할 수 있을 것 같다. 반응형
이라는 말은 결국 상태 변화에 반응한다는 뜻이고, 데이터의 상태를 바꿔주는 어떤 event가 일어났을 때 onEvent 메서드를 통해 이 이벤트에 반응해서 변화된 데이터 값을 이용해 후행 작업을 해주는 형식으로 동작한다.
쉽게 말하면 그냥 비동기 논블락킹을 어렵게 표현한게 결국 리액티브라고 생각한다. 그러니까 리액티브 프로그래밍(Project Reactor)은 결국 스프링 계열에서 우리도 한 번 다른 비동기 언어처럼 해보자!하는 마음에서 시작한 프로젝트인 것..
실제로 리액티브 스타일이 적립되기 이전에 JavaScript에서 promise, callback hell 등등 많은 문제를 겪고 async await으로 간 것처럼 자바 쪽에서도 Future, CompletableFuture(어떤 이벤트가 성공하거나 실패했을 때 값을 받아올 수 있는 객체) 등 수많은 혼란스러운 개념들이 등장했다가 사라졌다...
웹플럭스도 스트림을 바탕으로 한다. 즉 a라는 데이터가 이리저리 가공되어서 b라는 데이터로 산출되는 과정 중에 발생하는 값들을 매번 꺼냈다가 수정해서 다시 넣고.. 이런 과정을 반복하는게 명령형 프로그래밍의 스타일이라면, 웹플럭스 그리고 Java 8에서 도입된 Stream에서는 요 중간 과정을 파이프라이닝해서 하나의 관으로 쭈우욱 이어주고, 이전 파이프에서 받은 값을 변형해주고 싶을 때에는 람다식을 이용한다.
specialList.stream().map(value -> value + b)...
요런 식으로!
이런 방식의 코딩이 멋있다는 사람들도 간혹.. 있지만, 처음 접하는 사람들은 이게 뭐지? 싶을거고..(보다보면 또 적응되긴 한다)
코딩을 하는 데에 있어서 제약사항이 참 많아진다ㅠ
어떤 제약사항이 생기는데?
이걸 설명하기 전에 웹플럭스에서 스트림 방식(?)을 이용하는 이유를 먼저 설명하면, 결국 이것 역시 비동기-논블락킹이기 때문이다. 기존의 스프링 MVC 계열에서는
int a = b + 3;
a = a * 3;
가령 이런 간단한 코드가 있다고 했을 때, a = (b+3)*3이라는 걸 당연히 보장할 수 있다! 순차적으로 실행되기 때문에!!
그러나, 웹플럭스는 요청 당 쓰레드 모델이 아니고, 비동기 방식이기 때문에 코드가 순차적으로 실행된다는 보장 또한 없다!
따라서 위와 같은 기능을 하는 코드를 작성하기 위해서는, 스트림을 이용해서 코드를 파이프라인으로 이어주는 방식으로 코드의 실행 순서를 보장해줄 필요가 있는 것이다.
아무튼.. 상황이 그렇다보니 모든 동작을 이어서 작성해주고, 또 최대한 내부적으로 변수를 할당해서 데이터를 꺼내는 작업을 지양해야하는 웹플럭스 특성상 제약사항이 많이 생길 수밖에 없다..
나도 깔끔하면서 가독성 좋은 코드 짜고 싶다.. 정말..
Spring WebFlux 문서를 보다보면 동글동글한 그림이 있는 마블 다이어그램을 쉽게 볼 수 있다. 처음에는 더 알아먹기 힘든 이런 그림을 왜 그렸는지 모르겠다는 생각이었는데, 웹플럭스를 알면 알수록 정말 이게 딱 맞는 표현이었구나 싶다.
사실 나는 웹플럭스의 모든 Operator들, 스트림들이 차들이 달리는 도로 같다는 생각을 많이 한다. 그리고 이 도로에서 운반해야할 짐들이 정말 많이 있을 때, Mono는 이 짐들을 한번에 싣고 도로를 달리는 트럭이라면, Flux는 이 짐들을 하나씩 나누어 싣고 서로 다른 차선에서 달리는 승용차이다.
Spring WebFlux에서 코드를 짜다보면 그 놈의 object..를 많이 만날 수 있다. 왜 자꾸 object 타입으로 리턴될까? 한다면 Flux와 Mono를 왔다갔다 전환하는 과정에서 길을 잃어서 데이터가 여러개인데 모노로 리턴값을 지정해줬다거나 하는 문제일 가능성이 높다.
이때 이 짐을 데이터라고 생각한다면, Mono에서는 데이터 컬렉션, 예를 들어 List 같은 것이 한 번에 이동하고, Flux에서는 List의 요소요소들이 하나씩 따로 이동한다. 따라서 이 데이터들이 변환될 때에도 Flux는 하나하나가 따로 운반되면서 변환된다. (때문에 Flux에서는 이 요소들의 순서를 보장할 수 없고, 순서를 보장하면서 데이터를 넘겨주기 위한 flatMapSequential같은 오퍼레이터들이 존재한다.)
자바를 쓰다보면 진짜 별 걸 다 추상화해서(사용하는 쪽에서는 이게 어떤 식으로 구현되어있는지 알 필요 없이 그냥 기능만 알고 사용하면 된다) 제공해준다는 생각을 하는데, 그 중 하나가 바로 스케줄러이다. 웹플럭스에서는 쓰레드 스케줄링 또한 추상화해서 제공한다!
그게 바로 웹플럭스의 publishOn과 subscribeOn 메서드. 요 메서드들을 활용하면, 값을 넘겨주는 타이밍에, 혹은 어떤 데이터의 구독을 시작하는 타이밍에 쓰레드를 전환해줄 수 있다.