… reactive programming is a delcarative programming paradigm concerned wiht datat streams and the propagation of change - wikipidea
위 인용에서 키워드는 다음과 같다. 패러다임
, 변경 사항의 전파
, 데이터의 흐름
, 선언적 프로그래밍
위 정의가 언뜻 듣기에는 난해해 보이지만, 사실 반응형 프로그래밍은 아주 친숙한 개념이다. 가장 쉽게 반응형 프로그래밍의 패러다임을 이해할 수 있는 예제는 바로 스프레드 시트이다.
출처: https://velog.io/@teo/reactive-programming#3-반응형-프로그래밍-deep-dive
*C1
셀에 =A1+B1
이라는 수식을 선언적으로 작성해 두면, A1
혹은 B1
이 변경될 때마다 변경사항이 전파되어 C1
의 값이 자동으로 변경되는 것을 볼 수 있다. D1
에는 =A1+B1+C1
이라는 수식을 선언적으로 작성해 두었는데 역시 A1
혹은 B1
이 변경될 때 변경사항이 전파되어 C1
의 값이 변경되고 다시 그 값이 D1
의 수식에 반영이 되어 D1
의 값이 변경이 되는 데이터의 흐름이 만들어졌다.*
이러한 개념을 조금 더 확장해서 프로그래밍을 할 수 있다면 좋지 않을까 하는 패러다임이 바로 반응형 프로그래밍
이다.
웹 개발에서 반응형 프로그래밍이 중요해진 계기는 바로 웹 프레임워크로의 전환이다.
💡 `jQuery`에서 `React`와 같은 웹 프레임워크(라이브러리)로의 전환은 DOM을 더욱 쉽게 조작하도록 도와준다. 편한 방식으로 컴포넌트를 구축하면 알아서 DOM으로 변환한 뒤 렌더링 해준다.웹 프레임워크의 본질을 반응형 프로그래밍 패러다임의 관점에서 다시 적어보자.
💡 **Web Framework의 본질(MVVM Pattern)** Change detection + Binding + Template + Auto render = Reactive이렇게 정리를 해보니 평소 자주 사용하던 웹 프레임워크가 반응형 프로그래밍과 매우 닮은 점이 많다는 것을 느낄 수 있다. 웹 프레임워크의 등장은 기존의 방식을 뒤엎는 혁신적인 패러다임이었기 때문에 웹 프론트에서 아주 중요한 개념이 되었고 이를 정의하는 Reactive Programming
이라는 용어의 관심도가 높아졌다.
초기 Reactive Programming
패러다임은 이렇게 데이터의 변경을 감지하고 선언적으로 프로그래밍을 하는 방법을 통해 View를 업데이트 하는 방식으로 발전했다.바
이렇듯 웹 프로그래밍은 데이터를 가져와서 화면을 만드는 방식에서 무엇을 할지 선언을 한 뒤 변경된 데이터를 감지하고 전파하는 방향으로 패러다임이 변했다.
초기에는 이 반응형 프로그래밍 패러다임이 뷰에 집중되어 있었다면 최근에는 뷰를 넘어 비즈니스 로직을 포함한 모든 스크립트에서 사용할 수 있도록 개념이 확장되었다.
이후 Redux를 필두로한 상태관리라고 하는 것들 역시 데이터의 변경을 감지하고 변경을 전파해서 선언적으로 값을 만들어낸다는 반응형 프로그래밍 패러다임에 속하게 된다.
JS는 비동기 로직을 다루기 위해 많은 진화를 거쳐왔다. 기존 콜백 지옥에서 Promise
개념이 등장해서 체이닝으로 처리할 수 있게 되었고 ES7이후에는 async-await
개념이 등장했다. 하지만 과연 이정도로 충분할까? 다음 Real World Problem을 살펴보자. 요구사항은 아래와 같다.
Promise
와 aysnc-await
으로 이 문제를 해결할 수 있을까요? 기존 pull기반으로 비동기 순서를 맞추는 방식의 패러다임에서는 비동기가 복합적으로 존재하면 개발 난이도가 비약적으로 상승합니다.
때문에 이렇게 복합적인 비동기 로직을 처리할 수 있는 새로운 패러다임의 필요성이 부각되었습니다.
반응형 프로그래밍은 변경을 감지하고 전파하고 선언적으로 프로그래밍을 작성한다는 패러다임을 구현하기 위해서 아래와 같은 구조를 갖는다.
addEventListender가 이렇게 동작한다.
DOM에서 사용하는 이벤트 리스너를 등록하고 전달하는 방식 역시 반응형 프로그래밍이다. 이러한 방식으로 인해 웹 프레임워크는 자연스레 반응형 프로그래밍의 구조를 따르게 되었다.
반응형 프로그래밍이 지향하는 것은 모든 스크립트에서 이러한 Event Listener의 관점으로 프로그래밍을 하는 것이다.
왜 Event 방식으로 개발을 하나요?
https://cycle.js.org/streams.html#streams-reactive-programming
여기 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를 먼저 고려하자는 패러다임이다.
이러한 방식은 모듈간의 결합이 복잡해질 수록 더 유효하다.