회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
설계에 관한 이야기를 먼저 쓰려고 했는데 먼저 설계의 원칙이라고 할 수 있는 프로그래밍 패러다임에 대한 설명이 선행이 되어야 할 것 같아, 현재 제가 쓰고 있는 개발 패러다임인 ’반응형 프로그래밍(Reactive Programming)’에 대한 이야기를 해보고자 합니다.
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
설계에 관한 이야기를 먼저 쓰려고 했는데 먼저 설계의 원칙이라고 할 수 있는 프로그래밍 패러다임에 대한 설명이 선행이 되어야 할 것 같아, 현재 제가 쓰고 있는 개발 패러다임인 ’반응형 프로그래밍(Reactive Programming)’에 대한 이야기를 해보고자 합니다.
객체지향 프로그래밍이나 함수형 프로그래밍과 같은 개념의 내용이라 깊게 파고들면 방대한 내용이니 최대한 단계별로 이해할 수 있게 풀어서 적어보았으나, 이해가 안 되더라도 그냥 재미있는 개발 이야기 정도로만 소비해주시면 좋을 것 같습니다.
“관점을 바꾼다는 것은 매우 어려운 일이다.
... 새로운 패러다임은 기존과는 다르게 생각하는 방법을 알려줄 것이다.
그리고 아마도 예전 방식으로 절대 돌아가지 않을 것이다.”
오늘 해볼 주제는 ‘반응형 프로그래밍(Reactive Programming)’에 대한 이야기입니다. 우선 위키피디아로 가서 한 번 ‘Reactive Programming’이 무엇인지 검색을 해보겠습니다.
… reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. -Wikipedia ... 반응형 프로그래밍이란, 데이터의 흐름과 변경사항의 전파에 중점을 둔 선언적 프로그래밍 패러다임이다. - 위키피디아 |
그렇다는군요. 전에도 언급했지만 컴퓨터 사이언스에서 정의는 이해해야 할 것이라기보다는 배워야 할 목차와 같은 거라고 했습니다. 따라서 이번 글에서는 다음과 같은 핵심 키워드인 패러다임, 변경 사항의 전파, 데이터의 흐름 그리고 선언적 프로그래밍을 머릿속에 넣어두시고 읽어 주시기 바랍니다.
패러다임(영어: paradigm)은 어떤 한 시대 사람들의 견해나 사고를 근본적으로 규정하고 있는 테두리로서의 인식의 체계, 또는 사물에 대한 이론적인 틀이나 체계를 의미하는 개념이다. – 위키피디아 |
역시 예시를 드는 게 제일 좋겠죠. 모두가 이해할 만한 좋은 패러다임과 관련한 예시로는 지동설과 천동설이 있습니다.
지구가 중심이라는 관점을 가지고 우주의 움직임을 설명하는 천동설과 태양이 중심이라는 관점으로 우주의 움직임을 설명하는 지동설이 존재했습니다. 이렇듯 어떠한 관점을 가지느냐에 따라 같은 우주 움직임을 전혀 다른 2가지의 방식으로 설명을 할 수 있던 시절이 있었습니다. 흔히 지동설이 옳고 천동설이 틀린 거라는 생각하는 사람들이 있지만, 실제로 그러한 것은 아니라고 합니다. 정말로 관점의 차이만 다를 뿐 둘 다 충분히 우주의 움직임을 설명할 수 있었다고 합니다. (참고: 8-02. 천동설은 지동설보다 열등한가?)
다만 이후 지동설이 주류가 된 것은 천동설보다 훨씬 더 간결하고 단순하게 설명을 할 수 있는 관점이기 때문이라고 합니다. 덕분에 우리는 조금 더 간결한 방식으로 우주를 바라보고 설명할 수 있게 되었습니다.
프로그래밍 패러다임은 프로그래머에게 프로그래밍의 관점을 갖게 해 주고, 결정하는 역할을 한다. 예를 들어 객체지향 프로그래밍은 프로그래머들이 프로그램을 상호작용하는 객체들의 집합으로 볼 수 있게 하는 반면에, 함수형 프로그래밍은 상태 값을 지니지 않는 함숫값들의 연속으로 생각할 수 있게 해 준다. - 위키피디아: 프로그래밍 패러다임 |
천동설과 지동설의 예시와 같이 내가 프로그램이라고 하는 것을 어떠한 관점으로 바라보고 설계를 하느냐에 따라, 같은 목적을 이루고자 하더라도 전혀 다른 형태로 프로그래밍을 할 수 있도록 해준다고 생각합니다.
또한 잘 만들어진 프로그래밍 패러다임은 보다 좋은 프로그램을 만들 수 있는 방법과 시각을 제공해줍니다. 그래서 우리가 다양한 프로그래밍 패러다임을 알고 있다면 프로그램을 이해하는 데 훨씬 더 도움을 받을 수 있게 됩니다.
우리가 객체지향 프로그래밍이라던가 함수형 프로그래밍에 대해서 배우는 것들도 그러한 맥락입니다. 나아가 여러 가지 패러다임을 알고 있다면 그 중에서 더 간결하고 단순하게 설명하고 있는 관점을 선택적으로 취할 수 있게 됩니다.
현대에 와서는 하나의 프로그램을 하나의 패러다임만이 아니라 여러 패러다임을 함께 섞어서 사용하려는 시도가 늘고 있습니다. 특히 웹 프론트의 자바스크립트야말로 여러 가지 패러다임의 공존의 장이기에 다양한 프로그래밍 패러다임을 익히는 것은 더 좋은 프로그램의 설계를 할 수 있는 토대가 되어줍니다.
... 순수한 관점에서 이질적으로 보였던 패러다임 간의 공존이 갈수록 많이 등장하며, 상황과 맥락에 따라 패러다임 간 장점만을 취하려는 시도는 계속되고 있다. - 위키피디아: 프로그래밍 패러다임 |
반응형 프로그래밍도 객체지향 프로그래밍과 함수형 프로그래밍과 같이 프로그래밍을 바라보는 관점을 갖게 하고 결정을 하는 역할을 하는 체계라고 이해하시면 좋을 것 같습니다.
반응형 프로그래밍이란, 데이터의 흐름과 변경사항의 전파에 중점을 둔 선언적 프로그래밍 패러다임이다. |
정의가 언뜻 듣기에는 어려워 보이나 사실 반응형 프로그래밍은 아주 오래되고 친숙한 개념입니다. 가장 쉽게 반응형 프로그래밍의 패러다임을 이해할 수 있는 예제는 바로 스프레드 시트입니다.
예시에 있는 C1의 셀에는 =A1+B1 이라는 수식을 선언적으로 작성을 해두고 A1 B1의 값을 변경을 하면 즉시 변경사항이 전파되어 C1의 값이 자동으로 변경이 됩니다.
D1에는 =A1+B1+C1 이라는 수식을 선언적으로 작성을 해두었고, A1 B1의 값을 변경하자 변경사항이 전파되어 C1의 값이 변경되었고 다시 그 값이 D1의 수식에 반영이 되어 D1의 값이 변경이 되는 데이터의 흐름이 만들어졌습니다. (저는 이러한 순서를 정의한 적이 없습니다.)
이러한 개념을 조금 더 확장해 ‘이러한 관점으로 프로그래밍을 할 수 있다면 좋지 않을까?’에서 시작한 패러다임이 바로 반응형 프로그래밍입니다.
웹 개발에서 반응형 프로그래밍이 중요해진 계기는 다들 알고 있는 웹 프레임워크로의 전환입니다.
웹 개발에서 가장 큰 패러다임의 전환, jQuery → 웹 프레임워크 = DOM을 내가 쉽게 조작하게 도와준다. → DOM을 알아서 렌더링 해준다. |
웹 프레임워크의 본질을 반응형 프로그래밍 패러다임의 관점에서 다시 한번 적어봅시다.
Web Framework의 본질 (MVVM 패턴) Change Detection + Binding + Template + Auto Render = Reactive 즉, 값이 변경되었을 때 템플릿에 선언해둔 대로 알아서 렌더링을 해준다 = 반응형 |
어떤가요? 우리가 본 반응형 프로그래밍의 정의와 유사하지 않은가요?
웹 프레임워크는 기존의 프로그래밍 방식과는 혁신적인 패러다임의 등장이었기에 웹 프론트에서 아주 중요한 개념이 되었고, 이를 정의하는 Reactive Programming이라는 용어의 관심사가 올라가기 시작했습니다. 초기 Reactive Programming의 패러다임은 이렇게 데이터의 변경을 감지하고 선언적으로 프로그래밍을 하는 방법을 통해 View를 업데이트를 하는 방식으로 발전을 하기 시작합니다.
선언적 프로그래밍이란? - 무엇을 해야 할지 따로 약속(표현)을 만들어 기술하게 하고, 언제 어떻게 동작하는지는 내부에 처리하는 방식의 프로그래밍 기법 |
웹 프레임워크의 핵심은 JS를 통해 어떻게 DOM 조작을 할지가 아니라 JSX 혹은 Template Binding을 통해서 무엇을 해야 할지 선언을 해두면 내부적으로 알아서 적절하게 렌더링을 하는 방식입니다.
언제 어떻게 해야 할지 내부 메커니즘은 숨기면서 필요한 로직만 외부에서 선언을 할 수 있게 함으로써 알고리즘을 구현을 하는 데 발생하는 에러를 최소화하고 시간과 변화에 구애받지 않고 개발을 할 수 있도록 해줍니다.
<!-- svelte로 엑셀 시트 예제 만들어 보기 -->
<!-- 아무런 로직도 코딩하지 않았지만 동작한다. -->
<script>
let a = 0, b = 0;
</script>
<input type="number" bind:value={a}/>
<input type="number" bind:value={b}/>
<div>{a + b}</div>
한 번 여기서 위의 코드를 복사-붙여넣기 해보세요. |
어떻게 화면에 그릴지를 중심으로 개발을 하다가, 이렇게 무엇을 할지만 선언을 하는 방식으로 변경이 되면서 데이터를 대하는 패러다임도 변경이 됩니다. 기존에는 DOM 조작을 통해 화면을 구현을 하는 것이 중심이니 Render를 해야 하는 시점에 필요한 데이터를 모두 불러와서(Pull) 화면을 출력을 하는 Pull의 관점에서 UI 개발을 하였습니다.
하지만 새로운 반응형 프로그래밍의 패러다임에서는 미리 선언이 되어 있는 구조에서 값이 변화할 때마다 템플릿으로 데이터를 전달(Push)을 하는 Push의 관점으로 설계가 되도록 변화가 되었습니다. (React를 하는 분이라면 setState를 떠올려 주세요.)
이렇듯 웹 프로그래밍은 데이터를 가져와서 화면을 만드는 방향에서 무엇을 할지 선언을 하고 변경된 데이터를 감지하고 전파하는 방향으로 패러다임이 변해왔습니다.
어떤가요? 스프레드 시트와 웹 프레임워크를 통해서 반응형 프로그래밍이 어떠한 개념인지 감이 좀 잡히시나요? 사실 패러다임이 먼저 존재하지는 않습니다. 이러한 현상이 먼저 있고 그것들을 후에 하나의 관점으로 정리를 한 것이 패러다임이죠. 이러한 라이브러리와 방법들이 먼저 존재를 했었고 이후 Reactive Programming이라는 것이 정립이 되는 것입니다. |
그렇다면 우리는 이제 프레임워크를 당연히 쓰는 시대이니 반응형 프로그래밍을 하고 있는 걸까요? 틀린 말은 아니지만 다시 한번 구글 트렌드의 그래프를 확인해 볼까요? 반응형 프로그래밍은 그 이후에 개념이 확장되며 더 두각을 나타나게 됩니다.
초기에는 이 반응형 프로그래밍 패러다임을 뷰에 집중이 되었다면 이후에는 뷰를 넘어 비즈니스 로직을 포함한 모든 스크립트에서 사용할 수 있도록 개념이 확장이 됩니다. 이후 Redux를 필두로 한 상태 관리라고 하는 것들 역시 데이터의 변경을 감지하고 변경을 전파하고 선언적으로 값을 만들어낸다는 반응형 프로그래밍 패러다임에 속하게 됩니다.
이번 글에서는 반응형 프로그래밍 패러다임의 끝판왕 격인 Rx를 통해서 조금 더 이야기를 해보려고 합니다.
왜 프론트엔드 프로그래밍에서 반응형 프로그래밍이 중요할까요? 그것은 비동기 프로그래밍을 잘하기 위해서입니다.
비동기 프로그래밍은 상당히 어렵습니다. 하지만 대부분의 프론트엔드 로직은 비동기로 되어 있습니다. 프론트엔드는 사람을 상대하는 UI를 만드는 개발자이며 사람의 의도는 어떻게 동작할지 예측할 수 없기 때문입니다. 또한 우리는 서버를 상대하는 사람이며 서버의 응답이 언제 어떻게 올지 예측할 수 없기 때문입니다. 그렇기에 고급 프론트엔드 개발자가 되기 위해서는 이 비동기를 아주 잘 다룰 수 있어야만 합니다.
비동기 프로그래밍은 다음과 같은 이유로 어렵습니다.
1) 비동기 프로그래밍은 작성한 코드 순서대로 동작하지 않는다. 2) 언제 실행이 될지 예측할 수 없다. 3) 호출한 순서대로 동작한다는 보장도 없다. 4) 그래서 호출 당시의 값과 실제 실행되었을 때의 값이 그대로일 거라는 보장이 없다. |
• ES5 이전 시설 - 그저 Callback (Callback Hell에서 우리를 구원하소서!)
• ES6 Promise
• ES7 async / await
JavaScript는 최대한 비동기 로직을 동기 프로그래밍처럼 동작할 수 있는 방향으로 진화를 했습니다. 하지만 이것으로 충분할까요?
당신은 아래와 같은 자동완성을 구현해 달라는 요청을 받았습니다. 어떻게 코딩할지 상상해 봅시다.
|
Promise, async/await 이 이 문제를 해결할 수 있을까요? 기존 pull 기반의 비동기 순서를 맞추는 방식의 패러다임에서는 비동기가 복합적으로 존재하면 개발 난이도가 비약적으로 어려워지게 됩니다.
전통적인 백엔드에서는 하나의 request에서 비동기가 여러 개가 있더라도 순차적으로 처리해 하나의 response를 만들어주면 되지만, 프론트엔드는 사용자의 동작과 서버의 동작의 비동기 동작이 혼재되어 있는 세상입니다.
어떻게 하면 이러한 복합적인 비동기 세상에서 조금 더 프로그래밍을 잘할 수 있을까요? Rx와 반응형 프로그래밍은 어떠한 방식으로 이 비동기 문제에 접근하는지 한 번 살펴보도록 하겠습니다.
반응형 프로그래밍은 변경을 감지하고 전파하고, 선언적으로 프로그래밍을 작성한다는 패러다임을 구현하기 위해서 아래와 같은 구조를 가지고 있습니다.
그렇습니다. DOM에서 쓰는 이벤트 리스너를 등록하고 전달하는 방식 역시 반응형 프로그래밍입니다. 이러한 방식으로 인해 웹 프레임워크는 자연스레 반응형 프로그래밍의 구조를 따르게 되었죠. 우리가 가고자 하는 방향은 모든 스크립트에서 이러한 Event Listener의 관점으로 프로그래밍을 하는 것입니다!
제가 이해를 돕기 위해서 계속 반응형 프로그래밍 패러다임 위에서 이러한 것들이 만들어졌다고 설명은 하고 있지만 사실은 반대입니다. 패러다임은 이미 만들어진 현상을 해석해서 하나로 정리한 관점이자 체계라는 점 다시 언급하고 넘어갑니다. |
반응형 프로그래밍에 대한 철학을 통해 만들어진 라이브러리입니다. 라이브러리가 흥하지는 않았지만 반응형 프로그래밍에 대한 철학을 잘 설명하고 있기에 한 번 원문을 읽어보시면 좋을 것 같아요. (출처: cycle.js) |
주의) 이제 프로그래밍 패러다임과 관련된 용어들이 많이 등장하다 보니 다소 어렵게 느껴질 수도 있습니다. 잘 따라와 주시길 바랍니다.
2개의 모듈이 있습니다. Foo모듈에서는 네트워크 요청을 받으면 Bar 모듈의 값을 증가하는 로직이 있다고 상상해보세요. Foo 모듈에서는 Bar 모듈에게 영향을 끼치므로 위와 같이 그림을 그려보았습니다. 코드로 표현하면 다음과 같습니다.
// Foo.js
import Bar
function onNetworkRequest() {
// ...
Bar.incrementCounter(value);
// ...
}
이 경우 상태를 변화하는 로직은 Bar에 존재하는데 동작은 Foo가 하도록 하고 있습니다. 더군다나 Bar에서는 Foo의 존재를 알 수가 없습니다. 이렇게 하면 Bar는 수동적인 모듈이 되고 Foo 모듈과는 강결합되어 Bar에서는 자체 상태 관리를 하지 못할뿐더러 수정이 필요한 모든 로직을 Foo에 공개하는 형식으로 만들어야 합니다.
어떻게 하면 이러한 문제를 수정할 수 있을까요? 이 접근 방식의 대안은 화살표의 방향을 반전시키지 않고 화살표의 소유권을 반전시키는 것입니다.
// Bar.js
import Foo
const incrementCounter = () => { ... }
Foo.onNetworkRequest((event) => {
// ...
incrementCounter(event.value);
// ...
}
이렇듯 모듈과 모듈 간의 결합 시 참조의 주체를 바꾸어 사용하는 방법을 제어의 역전(Inversion of Control)이라고 부릅니다. 이 접근 방식을 통해 모듈의 상태를 변화시키는 로직을 외부 모듈에 의존하지 않고 모듈 내부에서 처리함으로써, 보다 캡슐화와 느슨한 결합을 하기 용이하도록 할 수 있습니다.
반대로 Foo의 입장에서는 어떨까요? Foo 모듈에서도 로직을 수행하는 부분에 있어서 자유로워졌습니다. 이제 누구에게 어떻게 데이터를 전달해야 할지 몰라도 됩니다. 그저 데이터가 만들어지는 시점에 전달만 하면 됩니다. 클릭 이벤트라던가 Promise와 같은 경우가 그렇습니다.
const p = new Promise(resolve => {
//...
// 필요한 값이 만들어진 시점에 그저 던지면 된다. 누가 받을지 몰라도 된다.
resolve(value)
//...
})
// 전달받는 쪽에서는 실행 시점이나 순서를 몰라도 된다.
p.then(() => {
// ...
})
처음에 배웠던 데이터의 흐름, 변경사항의 전파, 선언적의 반응형 프로그래밍 패러다임이 기억이 나시나요? 복잡한 비동기 프로그래밍도 반응형 프로그래밍을 이용한다면 선언적으로 데이터의 흐름을 통해 간단하게 처리를 할 수 있게 됩니다.
이러한 제어의 역전 방식은 이미 DOM의 Event나 Observer 패턴, Pub/Sub 패턴과 같이 오래전부터 사용해오던 방식이었습니다. 책임의 분리는 미들웨어나 플러그인과 같은 방식에서도 이미 자주 쓰이던 방식이었습니다.
반응형 프로그래밍은 이러한 관점을 일부가 아닌 모든 스크립트에 적용을 해서 항상 Reactive를 먼저 고려하자는 패러다임입니다. |
이러한 방식은 여러 개의 모듈 간의 결합이 많아질수록 더 유효합니다.
기존의 패러다임에서는 왼쪽의 그림처럼 하나의 모듈에서 여러 가지 비동기 코드를 호출하는 pull 방식의 개념을 통해 호출 순서와 실행 순서와 데이터 처리를 한 번에 하는 것이 관리를 어렵게 만들었다면, 반응형 프로그래밍에서는 오른쪽 그림과 같이 push를 통해서 하나의 모듈(함수)에서 하나의 화살표만 처리하면 되는 단순한 형태로 생각을 할 수 있게 됩니다.
이 내용이 없어도 Rx로 빌드업을 하는 데는 문제가 없을 것 같아 넣을까 말까 고민을 많이 했지만, 이 글이 Rx가 아니라 반응형 프로그래밍에 관련된 글이기에 알고 있으면 좋을 것 같았습니다. 당장은 이해가 안 되더라도 너무 깊게 고민하지 않기를 바랍니다. |
그래서 Rx와 같은 반응형 프로그래밍 라이브러리를 이용해서 이러한 구조를 만들게 되면 보다 단순한 관점으로 개발을 할 수 있게 됩니다.
반응형 프로그래밍은 어떠한 라이브러리나 구현체가 아닌 하나의 관점일 뿐입니다. 하지만 구체적인 예시가 없는 설명은 이해를 어렵게 만듭니다. 이 글에서는 Rx를 통해서 반응형 프로그래밍을 설명할 것이지만 정작 Rx를 어떻게 쓰는지에 대해서는 설명하지 않습니다. (마치 객체지향을 Java를 통해 설명하듯이 말입니다.)
Rx 학습을 추천드리는 이유! Promise와 async await는 현재 자바스크립트를 배우는 사람에게는 비동기를 다루는 너무나 당연한 개념입니다만 초창기 자바스크립트에는 이러한 개념이 존재하지 않았습니다. Ajax가 보편화되고 Node가 생기면서 callback만 가지고서는 비동기 처리가 너무 복잡해짐에 따라 Promise라는 Library가 생겨났고 이것이 Javascript의 표준 제안에 등록이 되면서 결국 Javascript의 Native 한 기능이 되었습니다.
더 복잡한 비동기 프로그래밍을 해결하기 위한 Observable이라는 API 역시 현재 ECMA의 기본 기능에 탑재 요청이 올라가 있는 상태입니다. 어떻게 될지는 모르겠지만 만약에 Native 한 기능이 된다면 Javascript 비동기의 개발의 판도가 바뀔지도 모릅니다. 그 전신이 될 수 있는 Rx를 실무에 쓰지는 않더라도 개념은 한 번쯤 배워보는 것은 어떨까요? |
ReactiveX An API for asynchronous programming with observable streams
|
Rx를 알고 나면 이 정의가 너무나 당연한 말인데 이해하는 과정이 좀 길고 복잡합니다. 비동기(asynchronous), 옵저버블(observable), 스트림(streams)이라는 키워드만 일단 기억을 해둡시다.
Reactive Revolution ReactiveX is more than an API, It's an idea and a breakthrough in programming. It has inspired several other APIs, frameworks, and even programming languages. |
왜냐하면 Rx는 단순한 API라기보다는 새로운 패러다임에 가깝기 때문입니다. 다시 한번 말하지만 이 글은 Rx가 아니라 반응형 프로그래밍 패러다임에 대해서 설명하는 글입니다.
앞선 내용을 차근차근 복습을 한 번 해봅시다.
비동기 프로그래밍은 실행 순서와 데이터를 제어하기가 어렵습니다. 하지만 프론트엔드는 비동기 프로그래밍 덩어리입니다. 그렇기에 조금 더 간결하고 쉬운 개발을 하기 위한 관점과 체계가 필요했고 그것이 바로 반응형 프로그래밍입니다.
반응형 프로그래밍은 변경사항의 전파와 데이터 흐름을 중심으로 선언적으로 작성하여 어려움을 해결합니다. 그러기 위해서는 Pull → Push의 패러다임 전환이 필요하고 이를 구현하기 위해서는 Event와 같은 방식을 통해 제어의 역전을 만들어야 한다고 하였습니다.
여기서 우리는 다음과 같은 깨달음을 하나 얻을 수 있습니다.
아하! 변경사항의 전파 + 데이터 흐름 = Event였구나! |
그래서 우리는 반응형 프로그래밍을 다음과 같이 확장을 할 수 있게 됩니다.
Event + 선언적 = 반응형 프로그래밍 반응형 프로그래밍이란 이벤트를 선언적으로 작성하는 프로그래밍 패러다임 |
Rx에서는 이러한 이벤트의 종류를 Next, Error, Complete의 3가지 type을 정의해서 조금 더 보편적인 상황을 처리할 수 있도록 정의하였습니다. Try, catch, finally와 같은 맥락으로 일단은 생각해두시면 좋을 것 같아요.
반응형 프로그래밍이란 스트림을 선언적으로 작성하는 프로그래밍 패러다임 |
그렇습니다. 반응형 프로그래밍은 스트림을 선언적으로 작성하는 프로그래밍 방식이었습니다! ... 지금은 이해가 안 가는 게 당연합니다. 추가적 설명과 예시를 통해 이해해봅시다.
조금 더 돌아가겠지만 선언적 프로그래밍에 대해서 한 번 생각해봅시다. 여러분들이 제일 많이 알고 있는 것은 바로 for와 Array Method의 차이입니다. 일단 이 차이는 알고 있다고 하고 진행을 할게요.
우리는 for가 아니라. map, .reduce, .filter 를 사용하게 되면 Array를 선언적으로 다룰 수 있다는 사실을 알고 있습니다. 그러면 이러한 방식처럼 Stream(or Event)도 선언적으로 다룰 수 있지 않을까요?
= Strem(or Event)를 Array Method처럼 만들어서 사용해보자! |
조~금 억지스러운 예시를 하나 가져왔습니다.
다음 코드를 살펴봅시다. 랜덤 한 숫자 5개에서 짝수만 2개를 골라 출력하는 프로그램입니다.
// 랜덤 한 숫자 5개를 받아서 짝수를 2개 출력하기
Array.of(1,2,3,4,5) // = [1,2,3,4,5]
.map(x => parseInt(Math.random() * 10))
.filter(x => x % 2)
.slice(0, 2)
.forEach(x => console.log(x))
일단 잘은 모르겠지만 Array의 값 대신 Stream을 다루는 객체의 이름을 Observable이라고 지어봅시다. 그렇다면 다음과 같은 코드의 형태로 작성을 할 수 있을 것 같습니다.
// 랜덤 한 숫자 5개를 받아서 짝수를 2개 출력하기
Observable.of(1,2,3,4,5)
.map(x => parseInt(Math.random() * 10))
.filter(x => x % 2)
.slice(0, 2)
.forEach(x => console.log(x))
Observable는 Array가 아니라 Event라고 했으니 사용자가 클릭을 할 때마다 랜덤 한 숫자를 받아서 짝수를 2개 출력하기는 어떨까요?
// [클릭]할 때 마다 숫자를 받아서 짝수를 2개 출력하기
Observable.fromEvent(window, "click") // [... click, .... click, ... click]
.map(x => parseInt(Math.random() * 10))
.filter(x => x % 2)
.take(2)
.forEach(x => console.log(x))
또는 1초마다 랜덤 한 숫자를 받아서 짝수를 2개 출력하기는 어떨까요?
// 1초마다 숫자를 받아서 짝수를 2개 출력하기
Observable.timer(1000) // [1, ... , 2 ... , 3, ... 4, ...
.map(x => parseInt(Math.random() * 10))
.filter(x => x % 2)
.take(2)
.forEach(x => console.log(x))
축하합니다! 이제 우리는 비동기 프로그래밍을 스트림(Stream)을 통해 선언적으로 프로그래밍을 할 수 있게 되었습니다!
이렇듯 이벤트를 하나의 값으로 생각하고 이를 Array의 Method를 다루듯이 작성을 한다면 훨씬 더 직관적이고 간결하게 프로그래밍을 할 수 있을 것 같습니다. (실제로 1초마다 숫자를 받아서 짝수를 2개 출력하기를 그냥 구현을 하면 어떻게 코딩을 할지 상상해 보세요.)
우리가 알고 있는 일반적인 설계는 저렇게 화살표를 통해서 데이터가 전달되는 노드의 형태로 표현할 수 있습니다. 반응형 프로그래밍에서는 전체 프로그램의 복잡도를 고려하지 않고 각 노드를 하나의 스트림으로 정의하고 선언적으로 개발을 할 수 있게 됩니다.
반응형 프로그래밍의 두 번째 키워드인 데이터의 흐름(Data-flow)은 바로 이러한 구조를 의미합니다. 각자의 변경사항을 전파를 하도록 설계를 하면 알아서 복잡한 데이터의 흐름이 만들어지지만 내부를 들여다보면 단순한 형태의 프로그래밍을 할 수 있게 합니다. 특히 이벤트를 값으로 다룬다는 관점이 매우 중요합니다.
반응형 프로그래밍에서는 이러한 Data-flow를 함수형 프로그래밍을 통해서 구현을 하고 있습니다. 함수형 프로그래밍이란 원본 값을 함수를 통해서 전달을 하고 새로운 값을 만들어내는 조합이 다시 하나의 함수가 되는 형태의 구조를 의미합니다.
그래서 아래와 같은 형태로 이벤트를 통해 데이터가 전달되는 흐름을 중첩된 PipeLine을 통해서 전달을 할 수 있도록 도와줍니다.
다시 보는 ReactiveX의 정의 An - 관측 가능한 스트림을 통해 비동기 프로그래밍을 하기 위한 API |
반응형 프로그래밍이란 스트림을 선언적으로 작성하는 프로그래밍 패러다임 |
결국 반응형 프로그래밍의 최종적인 정리는 결국 이것입니다.
반응형 프로그래밍이란, 데이터의 흐름과 변경사항의 전파에 중점을 둔 선언적 프로그래밍 패러다임이다. = 모든 것을 스트림으로 간주하고 선언적으로 개발하는 것 |
(... 결국 결론은 맨 처음 정의랑 똑같죠? 이번에는 이해가 된 채로 정의가 다시 읽혔으면 좋겠습니다.)
Array도, Iterator도, Promise도, Callback, Event를 스트림이라고 하는 하나의 관점으로 간주하고 개발을 하는 패러다임이 바로 반응형 프로그래밍입니다.
당신은 아래와 같은 자동완성을 구현해 달라는 요청을 받았습니다. 어떻게 코딩할지 상상해 봅시다.
|
let autoCompleteRef
const input$ = fromEvent(inputElement, "input")
const enter$ = fromEvent(inputElement, "keydown").filter(event => event.key === "Enter")
const request = (data) => fromPromise(fetch("URL", ...))
// 1) 타이핑을 입력할 때마다 타이핑된 내용을 서버로 요청한다.
input$.pipe(
map(event => event.value),
distinctUntilChanged(), // 2) 내용이 같을 경우에는 서버로 요청하지 않는다.
debounce(500) // 3), 서버 부하를 줄이기 위해 타이핑이 .5초 내로 이루어지면 기다렸다가 입력이 없는 경우 서버로 요청한다.
mergeMap(text => request(text).pipe(
map(res => res.result)
tap(value => autoCompleteRef = res.value)
timeout(7000), // 4) 서버에서 7초간 응답이 없을 경우 타임아웃 에러로 취급
retry(3), // 4) 에러시 3회 재시도 그래도 문제라면 에러 메시지 노출
catchError(e => console.error(e)),
),
takeUntil(enter$) // 5) 엔터를 눌러 검색을 하게 되면 이미 요청된 서버 내용은 무시한다.
)
.subscribe()
객체지향 프로그래밍, 함수형 프로그래밍과 같이 프로그램 씬에는 여러 가지 패러다임이 존재합니다. 이러한 패러다임을 이해하면 프로그램을 작성할 때 일관성 있고 좋은 관점을 가질 수 있고 설계와 결정에 도움을 줍니다.
반응형 프로그래밍은 스프레드 시트와 같이 미리 선언된 방식에 데이터의 변경을 전파하여 프로그램이 동작하게 만드는 방식의 패러다임을 말합니다. 이러한 반응형 프로그래밍은 비동기 프로그래밍을 조금 더 간결하게 바라볼 수 있는 관점을 제공해줍니다.
프론트엔드는 특히 비동기 프로그래밍 덩어리이므로 반응형 프로그래밍 패러다임은 프론트엔드에서 중요한 패러다임이 되었습니다. 웹 프레임워크가 그러했고 이후 만들어진 상태 관리 역시 그러합니다.
패러다임이 먼저 생겨났고 그 패러다임에 맞춰 라이브러리나 프레임워크가 나오는 것은 아닙니다. 오히려 반대죠. 특별한 문제를 해결하기 위해서 만들어진 것들의 공통점들을 모아 하나의 관점으로 정리해서 바라보는 일종의 체계와 같은 것입니다. 그래서 반응형 프로그래밍의 관점에서 바라본다면 React도 React-Query도 Redux도 Recoil도 Svelte도 얼마든지 데이터의 흐름 변경의 전파 선언적 프로그래밍이라는 관점에서 바라볼 수 있습니다.
또한 제어의 역전, 책임의 분리, 느슨한 결합, Data-Flow Programming, 함수형 프로그래밍 Event를 값으로 생각하기, Everythis is stream과 같은 개념들은 Rx뿐만 아니라 다른 프론트엔드 라이브러리를 이해하고 프로그램을 이해하고 본인 프로젝트의 설계 방향을 정하는 데에도 큰 도움이 됩니다.
인터넷에서 반응형 프로그래밍을 검색을 하면 대부분 Rx를 설명하는 글이 대부분입니다. Rx는 좋은 라이브러리이기는 하나 필수는 아니기에 자칫 반응형 프로그래밍 = Rx와 같은 생각으로 이어질 것 같아 Rx의 소개는 정말 최소한으로 하였습니다. 반응형 프로그래밍은 구현체나 라이브러리가 아니라 비동기를 잘 다루기 위한 프로그래밍 관점 및 체계라는 점을 다시 한번 알려드립니다. |
객체지향을 하나의 포스트로 끝낼 수가 없듯이 반응형 프로그래밍에 대해서도 깊게 들어갈 수 있는 부분들이 있겠지만 이 글이 반응형 프로그래밍의 소개가 아니라 프론트엔드 전반의 프로그램과 라이브러리의 방향성과 관점을 약간이나마 알 수 있게 되는 계기가 되었으면 좋겠습니다.
반응형 프로그래밍(Reactive Programming)과 Rx