RxJS | (2) 2. 패턴과 옵저버블

young_pallete·2022년 11월 22일
1

RxJS

목록 보기
3/5

이 글은 RxJS로 배우는 반응형 프로그래밍을 읽으면서 제 해석대로 쓴 글이니, 참고하시며 읽으시길 권장드려요 🙂

🚦 시작하며

최근에 너무 아팠었어요. 😭
그래서 이제 완쾌한 지금 부랴부랴, 이제 다시 또 시작하기로 했답니다.
그럼, 다시 RxJS를 살펴볼게요.
이터레이터와 제너레이터 패턴, 그리고 옵저버블에 관하여 이야기를 할 예정이에요.

🚀 본론

이터레이터 패턴은 자바스크립트를 공부하셨다면 충분히 아실만한 내용이라 생각해요.
일관성 있게 데이터들을 순회 및 탐색하기 위해 사용하는 패턴이죠.
이를 통해, 객체, 맵 등 순회가 불가능한 자료구조 역시 순회를 가능케 해줍니다.

한 번 예제에 있는 코드로 설명해보면 충분할 것 같아요.

const BufferIterator(arr, buffSize = 2) {
	this[Symbol.iterator] = function () {
		let nextIndex = 0;
      
        return {
        	next: () => {
            	if (nextIndex >= arr.length) {
                	return { done: true };
                } else {
                	let buff = new Array(buffSize);
                    for (let i = 0; i < buffSize; i += 1) {
                  		buff[i] = (arr[nextIndex++]);
                    }
                    return { value: buff, done: false };
                }
            }
        } 
    }
}

const test = new BufferIterator([1,2,3,4,5,6]);

for (let i of test) {
	console.log(test); // [1,2] [3,4] [5,6]
}

이터레이터 패턴을 통해, 데이터 소스들은 순회에 있어 일관성을 획득할 수 있습니다. 다른 구조라 하더라도, 마치 같은 데이터 소스인 것처럼 같은 방식으로 순회할 수 있게 해주죠.

이처럼, 추가적인 반복 연산을 순회만으로도 해결할 수 있으며, 심지어 순회가 불가능한 것도 순회가 가능할 수 있도록 하는 게 이터레이터 패턴의 장점이에요.

그러나, 책에 따르면 이러한 이터레이터, 이터러블 프로토콜은 RxJS의 코어에 들어가지는 않는다고 합니다 (RxJS가 더 빨리 나왔다고 하네요.)
그러나, 많은 곳에서 여러 방식으로 적용하고 있으니, 아는 게 이해하는 데 좋겠군요 🙂

객체지향 접근법과 데이터 기반 접근법

RxJS는 데이터 기반 접근법으로 개발하기를 권장합니다.
이 말은, 데이터를 어떤 구조와 '분리'하며 사고한다는 이야기입니다.
어떻게 보면 함수형과 맞닿아 있다고 보는 게 이해하는 데 더 빠를 수 있을 것 같아요.
어떤 인풋이 들어와도, 아웃풋을 일정한 로직으로 처리하도록 순수함수로 작성하는 것이 함수형 프로그래밍이기 때문이죠.

데이터로 사고하기

결국 왜 데이터와 구조를 왜 분리할까요?
RxJS가 지향하는 데이터 기반 접근법은 결국, 모든 세계는 데이터가 중심이 되어 있다는 사실로부터 시작합니다.

모든 일련의 객체들은 사실, 데이터가 없으면 의미가 없기 때문이죠.
결국 프로그래밍을 한다는 것은, 원하는 결과를 도출하기 위해 동작하는 것일 뿐이며, 이 동작을 의미 있게 만드는 것이 바로 처리할 대상, 데이터라는 의미입니다.

데이터 주입

그렇기 때문에 데이터는 항상 로직의 주체이며, 이를 주입해주어야 합니다.
모든 결과물은 데이터를 기반으로 이루어지기 때문에, 데이터의 생명주기가 곧 그 동작의 시종과 직결됩니다.

그렇기 때문에 모든 객체는 데이터로 인해, 반응하게 됩니다.
그것이 바로 반응형 프로그래밍의 핵심입니다. 단지 데이터의 처리 결과에 따라 반응되는 것일 뿐입니다.

왜 이것이 좋을까요?
RxJS는 데이터에 대한 로직은 데이터 스트림의 파이프라인을 통해 처리됩니다.
따라서 객체는 본연의 핵심 동작 로직에 집중할 수 있습니다. 부가적인 Pre-process가 필요하지 않다는 것은, 언제든지 데이터에 대한 결합과 분리에 자유로움을 의미하기 때문이죠.

결과적으로 데이터의 소스들은 일련의 처리 로직을 통해 동일해질 수 있습니다.
이것이 만약 일련의 처리 로직들로 인해 일반화된 데이터로 같아진다면, 결과적으로 유지보수성과 안정성, 일관성에 있어서 더 많은 이익이 생기지 않을까요?

정리를 하자면, 결국 모든 것은 데이터로 사고한다는 측면에 집중하게 된다는 것입니다.
객체 지향 접근법을 무시한다는 것이 아닙니다. 다만 객체로부터 데이터에 대한 사고를 분리할 수 있게 한다는 것이 이 책을 보며 느낄 수 있던 RxJS가 가진 강점이 아닐까 생각합니다.

Rx.Observable

이제 슬슬 본격적으로 RxJS를 사용하려고 하는 것 같군요!

Rx.Observable은 서로 다른 타입의 데이터 소스에서 생성된 이벤트를 구독하는 것을 지원해주는 타입이라고 책에 명시되어 있습니다. 이는 RxJS의 5버전의 핵심이었는데요.

옵저버블 객체를 통해 여러 종류의 데이터를 핸들링할 수 있으며, 결과적으로 원하는 결괏값에 도달할 때까지 데이터 조작 및 변환이 가능해집니다.

따라서 데이터를 이해해야 합니다. 이 책에서는 3가지로 설명하고 있군요.

  • 시스템과 상호작용하며 발생하는 방출 데이터
  • 시스템(메모리)에 이미 올라와 있는 정적 데이터
  • 주기적, 최종적으로 발생하는 최종 데이터

결국 이러한 데이터 소스들이 어떻게 Rx.Observable에서 처리가 될까요?

옵저버블 생성

Rx.Observable은 옵저버가 수신할 알림을 보내는 객체입니다.
즉, 옵저버는 이러한 옵저버블을 구독함으로써, 데이터를 처리할 수 있도록 일종의 감시를 하는, 옵저버 패턴과 비슷한 방식을 채택하고 있어요.

이때, 옵저버는 이벤트들에 모두 비동기적으로 반응합니다. 따라서 유연하게 애플리케이션을 유지할 수 있습니다.

엔도펑터와 연산자

펑터는 다른 타입을 감싸는 타입을 의미합니다.
예컨대 map이 있죠. 특정 타입을 파라미터로 받아, 다른 타입으로 나올 수 있도록 합니다.
이때, Rx.Observable은 아무리 연산을 하더라도, 내부의 제네릭으로 규정된 타입이 바뀔지라도, 결국 카테고리가 동일합니다. 즉 엔도 펑터를 만족합니다.

이러한 엔도펑터 안에서 체이닝과 제너레이터를 통해 미래의 값을 도출합니다.
그렇기 때문에 메서드는 마치 그저 원하는 데이터를 얻기 위한 연산자처럼 동작합니다.

RxJS를 언제 사용하는가

어떻게 보면 가장 중요한 파트입니다.
개인적으로 도구는 사용자의 목적에 따라 적절히 선택되어야 한다고 생각합니다.

단순한 목적을 위해 고도의 비용을 들이는 것, 그것을 리소스 낭비라고 하기 때문이죠. 이 책에서는 4가지 범주로 다음과 같이 나눕니다.

범주데이터 소스 타입
단일값 + 동기문자, 숫자
다중값 + 동기문자열, 배열
단일값 + 비동기Promise
다중값 + 비동기이벤트이미터

이러한 범주에 따라, RxJS에서의 처리방식을 나눌 수 있는데요.
저는 다중값 + 비동기에 주목하고 싶어요.
이유는, 원래 비동기의 복잡성의 대안으로 RxJS를 배우고 싶기도 했고, 실제로 RxJS는 이러한 비동기의 복잡성을 줄여주는 대안이라고 초반에 설명했기 때문인데요!

RxJS에서는 이벤트 이미터를 래핑합니다.
이를 통해서 기존에 갖고 있던 구독 해제, 에러 처리 등에 대한 복잡한 로직을 간단화할 수 있게 됩니다.

const clickStream = Rx.Observable.fromEvent(
  document.querySelector('#google'), 'click')
	.map(e => e.currentTarget.getAttribute('href'))
	.subscribe(console.log); // http://www.google.com

구독하는 메서드는 결국 이벤트를 처리한 후 map의 결과 데이터를 받아 콘솔에 출력하게 됩니다. 비즈니스 로직은 파이프라인을 타고 결과적으로 반환이 되는데요. 나중에는 이에 대한 로직과 출력을 분리하는 방법이 또 나온다고 하네요!

결과적으로 원하는 데이터가 외부의 부가작용과 관계없이 도출될 수 있다는 점이 인상적이네요.

이터레이터 vs 옵저버블

여기가 좀 많이 인상깊은 구절이었어요.
이터레이터랑 옵저버블이 어쨌든 일관성을 획득하기 위해 비슷하겠거니 싶었는데, 둘은 엄연히 다릅니다.

이터레이터: pull 기반

이터레이터는 데이터를 pull하는 방식입니다.
결과적으로 pull된 데이터를 하나하나씩 next 메서드를 이용하여 가져오는 방식인 거죠.

이러한 방식은 자료구조를 추상화하여, 반복되는 형태의 데이터 소스에 대해 쉽게 대체할 수 있도록 합니다. 또한 연산들을 바로 가져올 데이터 값에 반영할 수 있다는 측면에서 지연평가의 장점을 획득할 수 있어요.

한편, 언제 데이터를 사용할지 알 수 없는 로직에는 취약해요.
해당 연산이 이벤트와 전혀 맞지 않는 때 발생한다면? 이때의 취약점이 발생합니다.

옵저버블: push 기반

옵저버블은 이터레이터와 다릅니다. push하는 방식인데요.
데이터를 직접 들고 전달합니다. 즉, 요청을 하여 가져오는 것이 아니에요. 결과적으로 옵저버블은 이에 대해 반응을 하게 되기 때문에, 언제 들어오는지 그 시점을 신경쓰지 않아도 됩니다. 단지 옵저버블에 전달하는 순간, 옵저버블은 그 시점에 반응하게 되니까요.

즉, 생산자와 소비자의 엄격한 분리를 통해 데이터 플로우를 간결하게 하는 장점이 발생합니다.

옵저버

결국 옵저버블은 어딘가로 전파하고, 처리됩니다.
즉 목적지가 있다는 것인데요.
이러한 일련의 데이터 플로우를 일관성 있게 처리하는 객체가 필요합니다.
그것이 바로 옵저버 객체가 하는 역할인데요. 실제로 이러한 매커니즘은 이터레이터와 옵저버 패턴의 결합이라고 보면 편할 듯 합니다.

next() 메서드는 결과적으로 데이터를 사용할지, 말지에 대한 여부를 전달합니다. 그렇다면 옵저버는 이 신호에 반응하여 처리를 하게 되는 것이죠.
마지막으로 subscribe 메서드를 호출하면 더이상 처리할 게 없음을 알리는 거죠. 따라서 Subscription 객체가 반환됩니다.

Observer API

사실상 이벤트 이미터의 방식과 비슷합니다.
옵저버블은 전파와 동시에 옵저버와 일종의 '관계'를 맺습니다.
이 관계는 구독 절차 동안 계속해서 옵저버를 감시하게끔 만드는데요.
결과적으로 옵저버는 옵저버블로 단방향적인 결과를 전달하게 됩니다.

옵저버블은 이때, 다음 이벤트, 혹은 에러 및 성공 여부를 포함한 API를 사용하여 옵저버를 생성시킬 수 있습니다. 이를 통해 thenableresolve, reject에 관한 로직 처리를 쉽게 달성할 수 있습니다.

마지막으로 unsubscribe를 통해 구독자는 손쉽게 옵저버 API의 생명주기를 제어할 수도 있죠.

옵저버블 모듈과 순수함수

옵저버블의 컨텍스트에서는 데이터에 대한 정보가 담깁니다.
따라서 올 때마다 어떻게 생성될지, 어디서 오는지를 관리할 수 있는데요.

이러한 범주에는 비즈니스 로직 역시 속합니다.
결국 이 데이터가 어떤 로직으로 처리될 것인지를 안의 로직에 명시해줄 수 있는 거죠. 이러한 처리는 여러 API를 통해 쉽게 제어하며 안전하게 상태를 유지할 수 있게 됩니다.

이것이 왜 좋은 처리 방식일까요?
제 생각에는 필자가 말하고자 하는 것은, 제어라는 측면에서 데이터 플로우를 관리할 수 있다는 측면인 것 같아요.

기본적으로 이벤트라던지, 이터레이터를 처리할 때에는 done에 대한 조건을 명시해주고, 이를 제거하거나 반복 호출을 종료해야 합니다.

하지만 현재 이벤트 이미터나 이터레이터들은 이러한 로직들을 처리하지 않고, 전적으로 사용자에게 위임하고 있어요. 그렇기 때문에 안정성이 떨어진다는 것인데요.

RxJS에선 옵저버블 모듈을 통해 이러한 API로 손쉽게 관리할 수 있어요.
나아가 부가작용 없이 미래의 로직을 그려나간다는 것은, 어떤 데이터가 들어와도, 원하는 로직을 전달할 수 있다는 확신을 줍니다.

따라서 순수함수와 RxJS의 결합은 선택 배경과 결과 구현 과정에 있어 결합도가 매우 높고, 거의 필수적이라고 보입니다.

🎉 마치며

이 부분을 읽고, 다시 또 되풀이하면서 서술을 해봤는데 3시간이 걸렸어요.
아직도 분명 낯선 부분이 존재하고, 흐름을 '얼추' 이해했지, 완전히 소화하지 못했다는 것을 깨달으며 놀랐어요.

하지만 분명 아깝지 않은 시간이었어요.
사실 RxJS의 필요성이 긴가민가할 때도 있었는데, 결국 반응형에서 어떻게 API를 통해 관리하는지를 잘 몰라서 발생한 결함이라는 것을 알게 됐습니다.

작은 프로젝트에서는 모르겠지만, 분명 시스템이 거대해질 수록, 비동기 연산에 대한 안정적인 관리에는 RxJS가 분명 매력적인 선택지라는 느낌이 들었어요.

그럼, 다음에는 3장인 핵심 연산자로 넘어가볼게요. 이상! 🌈

profile
People are scared of falling to the bottom but born from there. What they've lost is nth. 😉

0개의 댓글