안녕하세요. 꼬동입니다.
https://ggodong.tistory.com/281
위 글에서 저희는 웹 애플리케이션 개발을 하면서 발생하는 오류 3개를 마주했습니다.
- 입력 오류
- 상태 오류
- 로직 오류
여기서 입력 오류는 'RxJS가 해결하려고 했던 문제 1'에서 설명을 드렸습니다.
이번에는 상태 오류를 알아보도록 합시다.
위의 링크 글에서 웹 애플리케이션은 하나의 큰 상태 머신이라고 했습니다. 이러한 상태는 서로 유기적으로 연결되어 있죠.
A라는 상태 머신의 상태값이 B의 입력값이 될 수 있고, B의 상탯값은 C,D와 관련있는 상탯값일 수도 있습니다. 이렇게 A, B, C, D는 의존성이 존재합니다.
이런 의존성이 깊은 애플리케이션은 많은 문제를 야기할 수 있습니다.
- A의 인터페이스가 변경되면 B도 함께 변경되어야 합니다.
- A 상태를 확인하기 위한 인터페이스에 대한 의사소통 비용이 발생합니다. => A를 개발한 개발자와 이를 의존하는 B라는 개발자 사이의 의사소통이 계속해서 필요하다는 뜻입니다.
- 다수의 클래스가 A에 의존 관계가 있는 경우 A의 변경 여부를 반영하기 위해서 A의 상태를 모두 반영해야합니다.
이러한 문제를 디자인 패턴 중 하나인 Observer Pattern으로 해결이 가능합니다.
Observer Pattern
옵저버 패턴은 상태가 변경될 대상을 Subject라고 합니다. 그리고, 그 상태 변화를 관찰하는 대상을 Observer라고 합니다.
이 둘은 느슨하게 연결되어 있습니다. 서로 상호작용을 하지만 서로 잘 모르는 것이죠.
Subject는 Observer가 특정 인터페이스를 구현한다는 것만을 알 뿐이고, Observer는 언제든지 추가하거나 삭제할 수 있으며 새로운 타입의 Observer를 추가한다고 해도 Subject를 변경할 필요가 없어서 서로에게 영향을 미치지 않습니다.
또한, 데이터를 얻고자 하는 대상이 데이터를 직접 가져오는 방식이었지만 옵저버 패턴은 Subject로 부터 데이터를 받는 방식입니다.
간단하게 Subject가 상태가 변경되면, 관찰하는 Observer에게 자동으로 알려주고, 처리만 신경쓰면 됩니다.
옵저버 패턴의 흔한 예를 얘기하면 뉴스를 발행하는 신문사와 구독하는 고객으로 들 수 있습니다.
신문사(subject)는 고객(observer)을 등록하고 신문이 발행될 때, 알려줍니다.(notify)
class NewsPaper {
constructor() {
this._observers = [];
}
setNews(news) {
this.notify(news);
}
add(observer) {
this._observers.push(observer);
}
remove(observer) {
let idx = this._observers.indexOf(observer);
if (idx !== -1) {
this._observers.splice(idx, 1);
}
}
notify(news) {
this._observers.forEach(v => {
v.update(news);
}
}
}
class NewsScrapper {
update(news) {
console.log("스크랩");
}
}
class NewsReader {
update(news) {
console.log("독문");
}
}
let newsPaper = new NewsPaper();
newsPaper.add(new NewsScrapper());
newsPaper.add(new NewsReader());
newsPaper.setNews("도지코인 떡락");
newsPaper.setNews("프론트 엔드 개발자 만세");
RxJS는 무엇을 해결하는가?
옵저버 패턴을 많은 문제를 해결하려고 했습니다.
허나 옵저버 패턴은 상태를 전달하는 Subject의 데이터가 언제 종료되는지를 Observer들은 알 수 없습니다. 이를 해결하려면 서로 규칙을 정해야하는데, 이러면 다시 의존성이 강해집니다.
또한, 상태 변화에서 에러가 발생할 수 있는데, Subject에서 에러가 발생했다고 합시다. 이를 try / catch로 해결하고 상태를 Observer에게 Push 했다고 했을 때, Observer는 에러가 발생했던 값인지, 아니면 정상적인 값인지 알 수 었습니다.
Observer에 의해 Subject의 상태가 변경되는 경우도 문제가 될 수 있습니다.
class WriterAndReader {
constructor(newsPaper) {
this._newPaper = newsPaper;
}
update(news) {
this._newPaper.setNews(news);
}
}
newsPaper.add(new WriterAndReader(newsPaper));
newsPaper.setNews("주화 입마에 빠지게 됩니다.");
해당 클래스의 update 메소드는 반복적으로 뉴스를 생성하기 때문에 무한루프가 돌게 될 것이고, 브라우저가 힘들어하겠죠.
궁극적으로 코드의 복잡도를 증가시키게 됩니다.
RxJS는 위에서 설명한 문제를 해결하려고 노력했습니다.
RxJS에서는 전달되는 데이터는 모두 Observable 형태로 반환됩니다. Observable은 구독(subscribe) 과정 후부터 데이터를 전달받기 시작합니다.
const { fromEvent } = 'rxjs';
const click$ = fromEvent(document, "click");
click$.subscribe(function(v) {
console.log(v);
});
let newsPaper = new Subject();
newsPaper.add({
update: function(v) {
console.log(v);
}
});
RxJS의 Observable은 옵저버 패턴의 Subject와 닮았습니다. Observer는 RxJS에도 있고, 옵저버 패턴에서도 있습니다.
둘 차이라면, 옵저버 패턴의 Observer는 add라는 메소드를 통해 Subject에게 전달됩니다.
반면, RxJS의 Observer는 함수와 객체 둘 다 가능하며 subscribe라는 메소드를 통해 Subject에게 전달됩니다.
위에서 옵저버 패턴은 Subject의 에러, 종료에 대해서 한계점이 있다고 말씀드렸습니다.
RxJS에서는 error / complete 메소드를 이용해서 이를 해결할 수 있도록 하였습니다. 또한 위에서 주구장창 썼던, update 메소드는 next로 구분지었습니다. 왜냐면 시간의 축으로 데이터를 보기 때문에 연속적인 변화를 Obserer에서 표현할 수 있도록 바꿨습니다.
또, RxJS는 Observer로 부터 데이터를 받아 문제가 될 수 있는 (양방향 데이터 흐름)을 해결하기 위해서 구조적으로 개선했습니다.
Observable은 subscribe를 통해 데이터를 전달할 대상(Observer)에게 데이터를 전달할 수 있지만, 반대로 전달할 대상(Observer)으로부터 데이터를 받을 수 없습니다.
이렇게 데이터 흐름을 단순화하여 오류 발생 빈도를 줄이고, 단방향 데이터 흐름을 할 수 있게 했습니다.
Observer의 리액티브
RxJS는 데이터가 발생하면 Observer에게 자동으로 데이터를 전달합니다. 이를보고 리액티브하다고 하는데, 리액티브 프로그래밍의 경우 데이터 흐름과 상태 변화 전파에 중점을 둔 프로그램 패러다임입니다.
즉, 상태 변화의 흐름이 자동으로 전파되는 것을 리액티브하다고 합니다.
이를 통해 지금까지 고민했던 상태 오류에 대해 옵저버 패턴으로 해결했고, RxJS는 이 옵저버 패턴을 개선하여 애플리케이션의 모든 데이터를 리액티브하게 전달할 수 있게 해준다는 것을 알게 됐습니다.
다음엔 마지막 고민인 로직 오류를 고민해봅시다.
이상 RxJS가 해결하려고 했던 문제 2 였습니다. ^_^
'Web + APP > Angular' 카테고리의 다른 글
Async Pipe 뜯어먹기 (4) | 2021.05.30 |
---|---|
RxJS가 해결하려고 했던 문제 3 (6) | 2021.05.20 |
RxJS가 해결하려고 했던 문제 1 (0) | 2021.05.19 |
RxJS를 시작하기 전에 (0) | 2021.05.19 |
forRoot() / forChild() 당신은 누구신가요? (11) | 2021.05.13 |