MVC 디자인 패턴이 아닌 Redux의 주개념이자, 리액트를 사용하면서 컴포넌트끼리
데이터를 교류할 때, 글로벌 이벤트 시스템을 설정하는 방법인 Flux 디자인패턴에 대해 알아보고
왜 facebook에서 react와 함께 MVC가 아닌 Flux를 적용하게 되었는지 알아보자
MVC의 한계와 Flux의 등장
개발자라면 한번쯤은 MVC(Model-View-Controller) 디자인 패턴을 사용하여 개발을 해봤을 것이다
그만큼 이 패턴은 어플리케이션 개발에 있어 널리 사용되는 디자인 패턴인데
대채 어떤 부분의 문제가 있길래 Facebook은 Flux라는 디자인패턴을 정의하게 되었을까?
한계
컨트롤러는 모델의 데이터를 조회하거나 업데이트 하는 역할을 하며 모델의 변화는 뷰에 반영된다.
또한 사용자는 뷰를 통해 데이터를 입력하는데 사용자의 입력은 모델에 영향을 주기도 한다는 것이다.
여기서 문제는,
페이스북 같은 대규모 어플리케이션에서 MVC 구조가 너무 빠르고 점점 복잡해 진다는 것인데 페이스북 개발팀에 따르면 '구조가 너무 복잡해진 탓에 새 기능을 추가할 때마다 크고 작은 문제가 생겼으며 코드의 예측이나 테스트가 어려워졌고, 새로운 개발자가 오면 적응하는데만 시간이 한참 걸려 빠르게 개발을 할 수가 없는 상황이 발생하게 되었고 최종적으로 소프트웨어의 품질을 담보하기가 힘들어졌다' 라고 말한다.
페이스북의 알림 버그
페이스북에 로그인 했을 때, 우측 상단에 알림(1)이 있다면
열려있는 채팅창과 채팅리스트들 모두에 알림(1)이 있어야 하고
어느 한군데서 새로운 채팅을 읽었을 때 모든 곳에서 알림(1)이 사라져야 한다.
버그는 사용자가 메시지를 읽었음에도 불구하고 사라지지 않는 경우가 발생하였고 처음에 사라졌다고 해도 몇분 뒤 알림(1)이 다시 표출되어 새로운 알림이 온것같아 확인해보면 아무런 알림도 오지 않은 상태였던 것이다.
이 문제는 이미 복잡해져 있는 구조 많은 모델과 많은 뷰에서 이에 대한 처리를 각각 진행하다 보니 사이클이 꼬여버리는 현상 혹은 무한루프에 빠지는 현상도 생겨났다고 한다. 이 사이클에 대한 문제는 단순히 사용자 뿐만 아니라 페이스북 개발팀에도 이어졌다고 한다. 그래서 페이스북은 이 사이클을 벗어날 방법을 찾게 되었고 단순히 단기적인 해결책이 아닌 "시스템을 더욱 예측 가능하게 만들어서 문제점을 완전히 없애는 것"에 대한 방법을 고민하게 되었다.
근본적인 문제점의 발견
페이스북이 찾은 근본적인 문제점은 어플리케이션에서의 데이터 흐름이 있었다.
이전에는 뷰에 데이터를 렌더링하기 위해 데이터를 가지고 있는 모델이 뷰레이어로 데이터를 보냈을 것이다.
사용자와 상호작용이 뷰를 통해 일어났기 때문에 사용자의 입력에 따라 뷰가 가끔씩 모델을 업데이트 해야 할 필요가 있었을 것이고 의존성 때문에 하나의 모델만이 아닌 다른 모델을 업데이트 해야할 때도 있었다.
예) 공이 어디로 날아갈지 알기 어려운 게임, 공이 화면 밖으로 나갈지도 모른다
이런 변경이 비동기적으로 생길수도 있었고 위에서 말했던 것처럼 '하나의 변경이 다수의 변경을 일으킬 수 있었는데' 이 문제로 인해 데이터 흐름을 제대로 파악하고 디버그 하기 어렵게 만든다는 것이다.
해결책
*단방향 데이터 흐름 (unidirectional data flow)
그래서 페이스북은 다른 종류의 시스템 구성을 시도하기도 결정했는데, 이렇게 결정한 구조는 데이터는 단방향으로만 흐르고 새로운 데이터를 넣으면 처음부터 흐름이 다시 시작되는 방식으로 설계 되었고 이 시스템 구성 즉, 아키텍처를 <Flux> 라고 명명하게 되었다.
Flux 내부에서의 각각의 역할
<액션 생성자, the action creator>
액션생성자가 하는 일은 마치 전보기사와 같은데, 이 비유는 전보기사가 알파벳을 기계들이 처리할 수 있는 모스부호로 바꾸는 것처럼 "액션에 어떤 메시지를 보내야 할지 알려주면 액션 생성자는 나머지 시스템이 이해할 수 있는 포맷형식으로 바꿔주게 된다."
액션생성자의 상세 역할로는 타입, 페이로드를 포함한 액션을 생성하게 되는데 그중 타입은 시스템에 정의된 액션들 중 하나이고, 액션의 예로는 MESSAGE_CREATE or MESSAGE_READ 같은 것!
이처럼 하나의 시스템이 모든 가능한 액션들을 알게 됨으로써 갖는 효과는 새로운 개발자가 프로젝트에 합류해도 이렇게 구성된 액션생성자 파일만 열어보면 시스템에서 제공하는 전체 API를 바로 확인 할 수 있다는 점이다. 액션 작동방식은 일단 액션 생성자가 액션메시지를 생성한 뒤에는 디스패처로 해당 액션 메시지를 보내주게 된다.
<디스패처>
디스패처는 등록된 모든 전화회선 즉, 스토어들과 연결이 가능한 전화 교환대의 교환원 같은 역할을 한다. Flux의 디스패처는 다른 아키텍처들과는 달리, 액션의 타입과는 관계없이 등록된 모든 스토어에 액션을 보낸다는 것이다.
이말인 즉슨, 스토어가 특정 액션만 구독하는 것이 아니고 우선 모든 액션을 받은 뒤, 받은 액션을 처리할지 말지를 스토어에서 결정해야 한다는 뜻이 된다. 이처럼 디스패처는 액션을 보낼 필요가 있는 모든 스토어가 연결되어 있으며 액션생성자가 보낸 액션 메시지를 연결된 모든 스토어에게 전달해주게 된다. 그리고 Flux 디스패처의 처리방식은 '동기적'으로 실행되기 때문에 위에서 이야기했던 다수의 공으로 플레이하는 게임같은 경우를 처리하는데 도움을 준다.
동기적으로 처리 된다는 것은, 여러 액션이 들어와도 우선순위를 가진 액션 순으로 처리하기 때문에 비동기 적인 방식처럼 순서가 뒤죽박죽 되거나 복잡한 상황이 발생하지 않는 다는 것이다.
다만 주의할 점은
만약 스토어들 사이에 의존성 즉, 스토어들 끼리 연관이 있는 상황에서 우선순위가 높은 스토어보다 먼저 액션을 받아 업데이트 되야하는 스토어가 있을 수 있는데 해당 상황에 대해서는 waitFor() 라는 함수를 사용해서 해당 문제를 처리할 수 있다.
<스토어>
스토어는 어플리케이션 내의 모든 상태와 그와 관련된 로직을 가지고 있는데 이것은 마치 모든 것을 관리하는 정부관료와 같다.
모든 상태 변경은 스토어에 의해서 결정 된다. 그 다음, 모든 액션을 받은 스토어는 내부에서 '이 상태변경을 결정하기 위해' 보통 switch 구문을 통해 필요한 액션을 구분하게 되며, 구분되어진 액션 내용 중 상태변경에 대한 내용이 포함되어 있다면 액션에서 전달받은 페이로드를 통해 상태 변경을 진행한다. 만약 스토어에서 자체적으로 상태를 변경하고 싶은 경우 이를 처리할 수 있을까? 결론은 "안된다" 이다. 스토어는 결정을 할뿐 상태변경을 위한 요청을 스토어에 직접 보낼 수 없는 이유는 Flux의 기본 시스템 구성이 단방향 데이터 흐름인 것과 스토어에는 상태를 설정할 수 있는 설정자(Setter)가 존재하지 않는 것에 있다.
그러므로 상태 변경이 필요한 경우 변경을 요청하기 위해서는 반드시 처음부터 모든 정해진 절차(액션 - 디스패치 - 스토어)를 따라
상태 변경에 대한 액션을 보내야 한다. 스토어 상태를 변경 완료 한 후, 스토어는 변경 이벤트를 내보내게 된다. 이 이벤트는 컨트롤러 뷰에 상태가 변경되었다는 것을 알려주게 된다.
<컨트롤러 뷰와 뷰>
컨트롤러 뷰는 스토어와 뷰 사이의 중간 관리자 같은 역할을 하고
뷰는 컨트롤러 뷰에게 전달받은 내용을 사용자들에게 보여주는 발표자와 같다.
Flux 패턴에 대한 설명은 여기까지이고
아래는 위에서 * 표시한 단방향 및 양방향 데이터 바인딩에 대한 설명을 추가로 정리해보았다.
데이터 바인딩
두 데이터 혹은 정보의 소스를 일치시키는 기법으로
화면에 보이는 데이터와 브라우저 메모리에 있는 데이터를 일치시키는 것을 말한다.
MVC 디자인 패턴으로 예를 들자면,
모델과 뷰를 서로 묶어 모델과 뷰의 데이터를 자동 동기화 시킨다는 것으로 이해할 수도 있다.
<단방향 데이터 바인딩>
- 장점
- 데이터 변화에 따른 성능 저하 없이 DOM 객체 갱신이 가능하고
- 데이터 흐름이 단방향이기 때문에 코드를 이해하기 쉽고 데이터 추적과 디버깅이 쉽다.
- 단점 : 변화를 감지하고 화면을 업데이트하는 코드를 매번 작성해야 한다.
대표적으로 리액트가 단방향 데이터 바인딩을 한다.
단방향 데이터 바인딩은 데이터와 템플릿을 결합해 화면을 생성하는 것이다 (JS, HTML만으로 가능)
사용자의 입력에 따라 데이터를 갱신하고 화면을 업데이트 해야 하므로
단방향 데이터 바인딩으로 구성하면 데이터의 변화를 감지하고 화면을 업데이트 하는 코드를 매번 작성해주어야 한다.
리액트는 자바스크립트 기반으로 부모뷰 -> 자식뷰 바뀐 내용을 직접 전달한다.
<양방향 데이터 바인딩>
- 장점 : 코드량을 줄여준다
- 단점 : 변화에 따라 DOM 객체 전체를 렌더링 해주거나 데이터를 바꿔주므로 성능이 감소되는 경우가 있을 수 있다
- 흐름
- 컨트롤러에서 모델이 변경됨
- 뷰가 변경됨
- 뷰에서 스코프 모델이 변경됨
- 컨트롤러에서 모델이 변경됨
이렇게 컨트롤러와 뷰 양쪽 데이터 일치가 모두 가능한 것이 양방향 데이터 바인딩이다.
데이터의 변화를 감지해 템플릿과 결합해 화면을 갱신, 화면의 입력에 따라 데이터를 갱신하는 것이다.
양방향 데이터 바인딩은 데이터의 변경을 프레임워크에서 감지하고 있다가
데이터가 변경되는 시점에 DOM 객체에 렌더링을 해주거나 페이지 내에서 모델의 변경을 감지해 JS 실행부에서 변경한다.
입력된 값이나 변경된 값에 따라 내용이 바로 바뀌기 때문에 따로 체크해주지 않아도 된다.
양방향 데이터 바인딩은 웹 어플리케이션의 복잡도가 증가하면 증가할 수록 빛을 발한다.
수많은 코드의 양을 줄여줄 뿐만 아니라 유지보수나 코드를 관리하기 쉽게 해준다.
이렇게 Redux가 사용하는 디자인 패턴인
Flux Design Pattern과 데이터 플로우(단방향 및 양방향 데이터 바인딩)에 대해서도 정리해보았다.
Ref.
댓글