⚛️

Facebook팀이 React와 Flux를 선택하기 까지의 고민

Created
2022/06/04 01:29
Tags
Frontend
Common
Subtitle
#Facebook의 선택 #React #Flux pattern
페이스북팀은 2011년 뉴스피드 기능을 개발하며 2011년 가장 대중적인 아키텍처 중 하나 였던 MVC 를 포기하고 그들 만에 새로운 아키텍처를 도입합니다.
왜냐하면 그들은 이전부터 UI 개발을 하면서 두 가지 큰 문제를 직면하고 있었습니다.
1.
쉽게 잘 망가진다.
2.
빠르게 수정하는 것이 어렵다.
그리고 이러한 이유가 무엇인지 원인을 파악하던 중 예측 불가능한 코드가 이 문제의 근원이라는 결론을 내릴 수 있었습니다.
페이스북 팀은 기존 코드에 어떤 부분이 예측 불가능한 지 좀 더 자세히 살펴 보았고 아키텍처의 문제라는 가설을 세웠습니다. 기존의 MVCController 에 너무 많은 제어권이 존재 하였고, 그 모든 제어를 개발자가 직접 주관적인 판단하에 진행하는 형식이었습니다. 시스템이 아닌 개인에 의존하는 이러한 방식은 예측 가능성을 깨지기 쉬운(fragile) 것으로 만든다고 생각했기 때문입니다.
그렇기 때문에 그들은 DataView 에 시스템을 도입하는 것이 예측 가능성을 지켜주리라 생각했습니다.
그들은 예측 가능한 데이터를 위해 Flux 라는 아키텍처를 예측 가능한 UI 를 위해 React 라는 시스템을 도입합니다.
모든 어플리케이션은 DataUI 그리고 어떻게 이 두가지 기능을 제어 할 것인지에 대한 * 로 구성됩니다. 예를 들어 MVCControllerModelView 를 가능한 직접 관리(제어권을 갖는)하는 것을 의합니다. 페이스북 팀에 DataUI 를 나눈 것 또한 단지 모든 어플리케이션이 이렇게 구성되어 있기 때문입니다.

 동적인 프로세스와 정적인 프로그램의 갭

2014년 페이스북 Hacker way에서 Pete Hunt는 React에 대해 소개 합니다.
이 때, 에드거 다익스트라 박사의 인용문을 소개하는데 이는 다음과 같습니다.
“우리의 지적 능력은 정적인 관계를 정복하는데에는 준비가 되어 있으나, 시간에 따라 진화하는 프로세스를 시각화 할 수 있는 힘은 상대적으로 뛰어나지 않다. 이러한 이유로 정적인 프로그램과 동적인 프로세스 사이의 개념적인 격차를 단축하여 프로그램과 프로세스 사이의 대응 및 차이를 가능한 만큼 사소한 것으로 만들기 위해 최선을 다해야 한다.”
동적인 프로세스와 정적인 프로그램이라는 정의는 꽤 추상적인데 예시를 통해 쉽게 이해할 수 있습니다.
위의 UI는 현재 내 친구 상태의 목록을 가져오는 작은 탭입니다.
만약 이 탭에서 이러한 프로세스를 거쳤다고 생각해봅시다.
1.
Alice가 오프라인으로 변경되었습니다.
2.
Charles가 모바일로 재접속했습니다.
3.
Carol이 PC로 재접속 했습니다.
4.
Charles이 오프라인으로 변경되었습니다.
5.
Alice가 모바일로 접속했습니다.
6.
Dan이 모바일로 접속했습니다.
결과를 예측할 수 있으신가요? 물론 지금은 셀 수 있을 정도의 프로세스이지만 시간이 더 오래 걸리고 많은 프로세스 일수록 결과를 예측하는 것은 어려울 것입니다. 이와 같은 상황을 동적인 프로세스 라고 합니다.
하지만 현대 웹 프레임워크를 통해서 웹 개발을 하시는 분이라면 이러한 고민을 해보신 적이 없으실 것입니다. 그저 현재 상태에 대한 Render 를 데이터에 맞춰서 할 뿐이죠. 어떠한 프로세스를 거쳤는지 중요하지 않습니다. 현재 데이터만 중요할 뿐입니다.
즉, 정적인 프로그램이 가지는 하나의 상태에만 집중하는 것이죠.
이는 너무나도 당연해 보이지만 리액트가 탄생하기 전 동적인 프로세스와 정적인 어플리케이션의 시각화는 그 갭차이가 컸습니다. 그리고 그것이 예측 불가능한 UI를 만든다고 생각했던 것이죠.
사실 처음부터 이 갭이 생긴 것은 아닙니다. 적어도 90년대에는 REST API라는 도구를 대부분 사용했는데 이 도구는 아주 정적이었습니다. 데이터가 변경되면 그 때마다 refresh 하여 매번 새로운 화면을 가져오기만 하면 됐습니다.
그런데 여기에는 아주 중요한 성질이 있습니다. 바로 멱등성(idempotent) 이라는 성질입니다. 이는 요청이 같다면 항상 결과도 같다는 특징을 의미합니다. 1+1는 예외 없이 2라는 점과 REST API 에서 URI가 같다면 항상 그 화면은 같은 결과를 출력한다는 점이 그러함을 의미합니다.
그리고 이러한 멱등성은 앞으로 React의 발전에 있어 많은 영향을 주었습니다. 또한 멱등성은 함수형 프로그래밍의 참조 투명성(Referential Transparency) 과 같은 여러 개념에 기본이 됩니다.
하지만 스크립트와 비동기 HTTP 통신의 발달로 현대 웹은 더욱 긴 프로세스를 가지게 됩니다. 그리고 이러한 긴 프로세스는 예측 불가능성을 높였습니다. 더 이상 하나의 URI에 하나의 화면 만이 존재하지 않았습니다.
Pete Hunt와 리액트 팀은 REST API 가 그러했듯이 멱등성 을 유지하는 것만이 예측 가능성을 지키는 일이라고 생각했습니다.
데이터가 바뀔 때마다 DOM 요소를 수정하는 것은 멱등성을 해칩니다. 하나의 데이터에 여러 수정을 통해서 변경하는 점은 옳지 않습니다.
대신 데이터가 바뀔 때마다 완전히 새로운 DOM요소를 그리는 방법을 택합니다. 변경점이 발생하면 매번 새로운 데이터를 요청받고 매번 새로운 UI를 그려냅니다. 마치 데이터마다의 결과 스냅샷을 가지고 있는 듯한 REST API 가 그러했던 것 처럼요.
이러한 성질은 참조 투명성(Referential Transparency) 을 가진 함수들로 이루어져 있습니다. 그리고 이러한 것들이
1.
설명 가능한 순간적인 UI
2.
주어진 입출력에 관해 결과를 예측
3.
쉬운 테스트
와 같은 특징을 만들어낸다고 믿었던 것이죠.

 Stateful Brwoser와 Stateless한 Rendering

아까까지 설명한 아키텍처는 아주 뛰어난 것처럼 보이지만 몇 가지 문제를 가지고 있었습니다.
브라우저는 이미 동적인 프로세스( Stateful 혹은 상태)가 있는데, 어떻게 UI를 그리는 일은 상태가 없는 정적인 화면을 출력하는게 가능할까요?
가령 위와 같은 UI를 작성하기 위해서는 다음과 같은 코드를 작성했을 것입니다.
buttonAClick() { const buttonA = document.querySelector(".buttonA"); if(buttonA.isOff) { buttonA.style.backgroundColor = "red"; buttonA.style.innerText = "on"; } else { buttonA.style.backgroundColor = "gray"; buttonA.style.innerText = "off"; } }
JavaScript
복사
이를 매번 스크린 샷을 찍듯 매번 새로운 화면을 만들기 위해선 다음과 같이 변경되어야 할 것 입니다.
상태가 없는(stateless) 렌더링 방식입니다.
buttonAClick(color, text) { const buttonParent = document.querySelector(".buttonParent"); buttonParent.innerHTML = ` <button style="color:${color};">${text}</button> ` }
JavaScript
복사
더 이상 이전 버튼이 상태가 무엇인지에 따라 화면이 달라지는 것이 아닌, 순수하게 현재 데이터를 기반으로 작성되게 되었습니다.
이전 상태가 무엇이었는지 어떤 과정을 거쳤는지 더 이상 알 필요가 없기 때문에 예측 가능성을 지킬 수 있게 되었습니다!
다만 여기에는 두가지 큰 문제점이 있습니다.
1.
모든 것을 매번 새로 그리는 것은 성능에 대한 효율이 매우 떨어집니다.
2.
DOM을 새로 그리기 위해서는 일단 전부 지워야 합니다. 그려야 할 DOM이 복잡할 수록 유저들은 흰 화면을 오래 보게 될 것입니다. 즉 최악의 사용성을 제공하게 됩니다.
특히 유저의 사용성을 해치는 일은 엔지니어로써 절때 있을 수 없는 일입니다. 차라리 개발자가 불편함을 감수하는 것이 낫습니다.
이러한 문제를 해결하는 도중 Pete Hunt는 DOOM 3의 렌더링 엔진에서 아이디어를 얻습니다.
2004년 8월 3일에 출시한 FPS 장르의 비디오 게임

 개념적인 격차의 단축(shorthen the conceptual gap)

다익스트라 박사는 shorthen the conceptual gap 라는 표현을 썻습니다. “정적인 프로그램”과 “동적인 프로세스"의 갭 차이를 “물리적으로” 줄인다는 의미가 아닙니다. 현실적으로도 실제 존재하는 갭을 사라지게 하는 것은 불가능에 가까운 어려운 일입니다. 대신 개념적으로 이 격차를 줄이면 어떨까요? DOM을 매번 새로 만드는 것이 아니라, 작성 방법과 사고 방식은 DOM을 새로 만들 듯이 생각하지만 실제로는 DOM을 여전히 수정하는 것은 변함이 없는 거죠.
DOOM 3는 다음과 같이 구성되어 있습니다.
실제로 작동중인 Doom3가 존재합니다. 이는 Game의 상태와 유저의 인터렉티브를 입력받아 여러 네트워크 이벤트가 동작하여 상태를 변경시킵니다.
그리고 그 뒤에는 Game 세계(Game Logic)를 구성하는 논리적인 공간이 있습니다. 이 공간에서 게임이 어떻게 구성 하는지 선언적인 정보를 정의합니다.
그리고 이 정보들은 Scene IR(intermediate representation) 혹은 화면 중간 계층 이라고 불리는 곳에서 취합 됩니다. 선언적인 정보들을 바탕으로 어떤 무기를 들고 있고 어떤 공간에 있는지에 대한 그려야할 논리적인 설명들을 취하는 공간입니다.
마지막으로 Back End에서 논리적인 설명들을 바탕으로 그림을 그리기 위해 필요한 OpenGL작업으로 취합하여 Bus 를 타고 그래픽 카드로 취합됩니다.
리액트 팀은 이러한 아키텍처를 가져와서 필요한 부분을 적용합니다.
Virtual DOM 이라고 불리우는 추상화는 DOOM 3와 매우 유사합니다. 우리는 서버의 이벤트 혹은 브라우저에 이벤트에 의해 동작하는 어플리케이션을 가지고 있습니다.
그리고 비즈니스 로직을 설명하기 위한 React Component 를 선언적으로 작성합니다. 이러한 컴포넌트에 담긴 정보를 취합하여 논리적으로 표현가능한 가상의 DOM을 생성합니다.
그런 다음 OpenGL 작업 대신 명령형 DOM 작업을 계산하고, 그래픽 카드 대신 브라우저로 렌더링 합니다.
물리적인 렌더링 대신 논리적인 렌더링 계층을 추가하는 것은 개념적인 갭의 격차를 줄일 수 있는 아이디어가 되었습니다. 실제로 새로 그리지 않아도 Front End 에서는 마치 새로 그리듯이 Component를 만들면 됩니다. 나머지는 Virtual DOMBack End에서 물리적인 변경을 시도할 것입니다.
여기서 Front End 혹은 Back End는 “고유명사”로 쓰입니다. 일반적으로 직무를 나누는 단어를 뜻하는 것이 아니라 프로그램 내에 상대적인 표현으로 앞단과 뒷단을 표현하기 위해 사용 되었습니다.
최근 Svelete와 같은 신흥 프레임워크가 생겨나며 “Virutal DOM은 사실 빠르지 않다!” 와 같은 글을 종종 볼 때가 있습니다. 하지만 Virutal DOM 은 태생 자체가 퍼포먼스 개선이 목표가 아닌 개념적으로 멱등성을 가지는 View를 만들어나가는 과정에서 생긴 문제를 해결하기 위해 나온 아이디어 입니다. 때문에 당연히 순수 DOM보다 빠르지 않다고 말하는 것은 당연한 이야기 입니다.
결국 이러한 시스템을 통해서 리액트는 REST API 가 그래왔던 것 처럼 멱등성을 가지는 View를 생성할 수 있었습니다. 이러한 특징은 페이스북에 어떤 다른 개발자가 새로 프로젝트에 참여하더라도 View 코드를 쉽게 이해할 수 있었습니다. 뿐만 아니라 장애가 발생하거나 테스트 코드를 작성할 때도 동적이고 긴 프로세스 대신에, 순간 순간의 View 를 확인하면 문제를 해결할 수 있었습니다.
Virtual DOM과 같은 시스템을 만들어 내는 일은 대단한 일입니다. 하지만 우리가 알아야 할 그것보다 중요한 것이 있습니다. Pete Hunt는 React를 “Facebook doesn’t build Tech for tech's sake” 라고 표현합니다. 기술을 위한 기술을 개발하지 않는다는 것이죠! 지금도 우리는 새로운 기술이 아주 트렌디해서 도입하거나 배우는 경우가 있습니다만, 우리가 무엇을 만드는 지 무엇을 배우는 지 다시 한 번 고민하게 만드는 대목입니다.
멱등성의 개념이나 Virutal DOM 모두 기존에 있던 개념을 활용한 것이지 세상에 처음 나오는 것은 아닙니다. 리액트의 혁신은 사실 고전적인 컴퓨터 과학 이론과 다른 영역의 프로그래밍 기술의 핵심 개념으로부터 아이디어를 가져와 비슷하게 만드는 것임을 알 수 있습니다.

예측 가능한 데이터

Facebook 인프라 팀 소속인 Jing은 이전부터 골치덩어리인 화면을 알고 있었습니다.
바로 “안읽은 메시지” 기능이었죠. 이 기능은 때때로 이미 메시지를 다 읽었는데도 여전히 1 메시지가 사라지지 않았습니다. 유저들은 당연히 안 읽은 메시지가 남아있다고 생각해서 해당 버튼을 눌렀습니다. 하지만 매번 텅텅 빈 값만을 확인할 수 있었죠. 페이스북 개발팀은 이미 이 문제를 알고 있었고, 매번 엣지 케이스들을 수정했습니다. 하지만 잊혀질 때마다 좀비처럼 다시 살아나는 버그였죠. 계속 되는 수정 속에서 Jing과 팀원들은 더 이상 이 문제를 무너진 댐에 테이프를 붙이는 방식으로는 둘 수 없다고 판단했습니다.
문제를 찾기 위해서는 채팅 기능의 처음으로 잠시 돌아가 봅시다.

 새로운 메시지 컨트롤러

메시지 수신
채팅 기능 초기에 새로운 메시지가 전송되면 다음과 같은 컨트롤러 함수가 실행되었습니다.
function newMessageHandler(message) { var chatTab = ChatTabs.getChatTab(message.threadID); chatTab.appendMessage(message); }
JavaScript
복사
단순히 메시지에 해당하는 채팅방을 가져온 후에 채팅방에 메시지를 추가해주기만 하면 됬습니다.
읽지 않은 메시지 기능 추가
기존 기능에 읽지 않은 메시지 UI가 추가 되었습니다. 그래서 다음과 같이 컨트롤러도 수정이 필요했습니다.
function newMessageHandler(message) { UnseenCount.incrementUnSeen(); // 읽지 않은 메시지를 가진 스레드를 증가 시킨다. var chatTab = ChatTabs.getChatTab(message.threadID); chatTab.appendMessage(message); // 만약 Tabs이 Focus 되어 있는 중이라면 읽지 않은 스레드를 감소 시킨다. if(chatTab.hasFocus()) { UnseenCount.decrementUnseen(); } }
JavaScript
복사
메인 채팅 화면 추가
그러다 채팅 기능이 메인 기능으로 추가되었습니다. 이에 대한 수정 역시 변경되었습니다.
function newMessageHandler(message) { UnseenCount.incrementUnSeen(); // 읽지 않은 메시지를 가진 스레드를 증가 시킨다. var chatTab = ChatTabs.getChatTab(message.threadID); chatTab.appendMessage(message); // 만약 메인 채팅에 현재 메시지에 해당하는 스레드가 열려있다면 해당 스레드에 메시지를 추가한다. var messageView = Messages.getOpenView(); var threadID = MessagesView.getThreadID(); if ( threadID == message.threadID) { messagesView.appendMessage(message); } // 만약 Tabs이 Focus 되어 있는 중이라면 읽지 않은 스레드를 감소 시킨다. // 만약 새 메시지가 메인 채팅의 스레드라면 읽지 않은 스레드를 감소 시킨다. if(chatTab.hasFocus() || threadID == message.threadID) { UnseenCount.decrementUnseen(); } }
JavaScript
복사
Jing과 Facebook팀은 이러한 과정을 추적하며 구조적인 결함을 발견할 수 있었습니다.
위 어플리케이션은 fragile(깨지기 쉬운) 구조처럼 보였습니다. 왜냐하면
1.
새로운 기능이 생길 때마다 특정 개발자가 시스템이 기준이 아닌 주관적인 방식으로 개발 및 수정을 가한다. 이는 다른 개발자가 이해하고 수정하기 어려워 진다. 그리고 결국 만든 본인을 제외한 누구도 위 코드를 건들고 싶지 않아 한다.
2.
Controller에 역할이 비대해지고, Model과 View간의 복잡한 의존성이 내부에 존재하게 된다.
2번 같은 경우에는 Flux 혹은 Redux 에서 항상 나오는 MVC 혹은 양방향 바인딩의 문제점을 설명하는 것명과 동일합니다.
보시는 것과 같이 Chat , Thread , Message, UnseenMessageController 에서 취합해서 관리하고 그에 해당하는 View 들의 의존성을 서로 참조하는 것은 그 끝을 예측하기 힘듭니다.
가령 위의 다이얼로그에서 순환 참조 하는 ModelView 가 존재하나요? 아마 모든 화살표를 다 따라가봐야 그 결과를 알 수 있을 것입니다.
지금 당장 양방향 바인딩 이나 MVC 에 대해 복잡하게 생각하시지 않길 바랍니다. 천천히 기술보단 당장 해결해야할 문제에 집중하는 것이 추후에 조금 더 문제를 쉽게 바라볼 수 있게 도움을 줄 것입니다.

 새로운 아키텍처

결국 아키텍처를 손보기로 결정합니다. 그리고 첫 번째 수술 대상이었던 부분은 Controller 였습니다. ControllerModel 의 제어권을 가지고 개발자에게 직접 맡기는 방법을 고칠 필요가 있었습니다.
구체적으로 외부에 제어권(External Control)이 있는 상황을 문제라고 여겼습니다. 각각의 데이터들이 일관적으로 데이터를 유지해야 하는데 외부(Hanlder)에 제어권이 있다면 데이터들은 스스로 일관성을 유지할 어떠한 방법도 없습니다.
UnseenCounter의 개수나 Chat Tab의 메시지 수 모두 전적으로 Handler가 통제하며, 각각의 데이터들은 내부 일관성을 유지할 수 있는 기능이 없으므로 언제든지 데이터는 잘못될 수 있으며 스스로 고칠 방법도 없다는 것이죠.
그렇기 때문에 외부 제어권을 없애고 내부에 제어권을 부여하면 문제를 해결할 수 있으리라 생각 했습니다.
더 이상 Controller가 데이터를 제어하는 것이 아닌 각각의 데이터가 본인 스스로의 데이터를 관리하게 되므로써, 내부에서 일관성을 유지할 수 있는 기능을 제공하는 것이죠.
또 이러한 관점은 각각의 데이터가 본인의 역할만 충실하면 일관적인 결과를 얻을 수 있다는 점에서 SRP(단일 책임 원칙) 과 유사한 모습도 보이네요.
상태와 상태를 변경하는 코드가 한 곳에 있는 점에서 응집도 가 좋아졌다고도 표현할 수 있겠구요.
하지만 왜 기존에 다른 조직에서는 이러한 변화를 하지 않았을까요? 애초에 Controller 는 왜 필요했던 것일까요?
모든 도메인은 서로 간의 의존성을 가지고 있습니다. 이것은 자연의 이치에 가깝습니다. 채팅 기능도 마찬가지입니다. 읽지 않은 메시지는 Chat Tab혹은 Main Messages의 읽음 여부에 의존적 입니다. 서로가 서로를 모른다면 충분하지 않은 정보로는 표현할 수 없는 한계에 직면할 것입니다.
사실 서로 간의 의존성을 통제하고 관리할 즉 Controller 역할은 어떻게 보면 필수적인 것처럼 보입니다.
위 구조에서 적어도 확실한 점은 Unseen Counter는 정보가 불충분하다는 점이고 의존성이 있는 도메인으로부터 Unseen Counter 만을 위한 보다 더 명확한 데이터 (more explicit data) 가 필요하다는 점이죠.
이를 해결하기 위해서 Chat Tab과 같은 도메인의 View에서 이미 읽었음(Mark Seen) 과 같은 추가적인 트리거를 실행하고 새로운 사이클을 실행하여 문제를 해결할 셈이였습니다.
하나의 Controller 에서 처리해야하는 비즈니스를 도메인 별 분리더 구체적이고 많은 액션 으로 대체하면서 내부의 일관성도메인 간의 정보 부족 이라는 문제를 해결하려 한 것이죠.
그러기 위해선 기존에 파생된 데이터(derived data)를 사용했던 방식에서 더 명시적인 데이터(explicit data)를 사용해야 했습니다.
< derived data >
파생된 데이터는 존재하는 또 다른 데이터과 합성되었거나 추정된 데이터( 위의 그림 )라면 명시적인 데이터는 오로지 해당 도메인만을 위한 다른 데이터와 독립적인 성질을 지닌 데이터를 의미 합니다.
이 설명을 마치기 전에 먼저 조금 더 구체적으로 시스템을 변경해봅시다.
당연히 도메인 내에서의 데이터는 분리해야 한다는 것에는 의견이 없으리라 생각합니다.
전체 시스템은 아래와 같이 디자인 될 것입니다.
이와 같은 시스템에서 중요한 핵심적인 전략이 하나 있습니다. Data Layer 의 프로세스가 완전히 처리되기 전에 추가적인 Action 는 일체 전달 되서는 안됩니다.
위의 예시에서는 Messages, ThreadUnseen Threads 가 데이터 저장소에서 View 에게 알려줄 데이터를 모두 처리합니다. 그러고 나면 ViewMark Seen Action 을 호출할 수 있게 됩니다.
즉, 이러한 순서를 잘 지키는 것이 위 시스템에 핵심입니다.
이 시스템은 기존의 MVC 와 달리 한 방향으로 순서를 지키며 진행합니다. 그리고 이러한 특징들은 다이어그램을 더 일반화 할 수 있게 만듭니다. 양쪽 방향에 해당하는 서로에게 의존성을 표현해야만 했던 MVC 의 다이어그램과 달리 위의 시스템은 아무리 많은 ModelView 가 존재하더라도 그들을 하나의 StoresViews 로 압축 할 수 있습니다. 하나의 방향과 순서만 신경 쓰면 되기 때문입니다.
이러한 단순한 시스템과 하나의 방향에만 집중할 수 있게 되므로써, 하나의 기능을 추가하기 위해 더이상 전체 시스템을 건들일 필요가 없게 되었습니다. 특히 Jing은 새로운 팀원의 온보딩 기간동안 그들이 필요한 많은 것들을 설명할 필요가 없어졌습니다. 새로운 기능 혹은 새로운 맴버가 추가되는 것에 아주 큰 도움이 되었습니다. 혹은 새로운 맴버가 버그를 발견했더라도 어떤 액션이 시작된 위치만 알면 모든 downstream 을 파악할 수 있기 때문에 쉽게 버그의 원인을 찾을 수 있었습니다.
Jing은 하나의 방향에만 집중하기 때문에 다양한 모델과 뷰를 압축할 수 있고 이를 통해 “간단한 멘탈 모델” 을 형성할 수 있다고 말합니다. 그리고 이것은 Avoid cascading and propagates effects by preventing nested updates ( 중첩된 업데이트를 방지하는 것으로 계단식 혹은 전파 효과 방지 ) 를 돕는다고 합니다. MVC 가 서로의 ModelView 를 업데이트하며 복잡한 그래프형 화살표를 만드는 것이 더 이상 필요 없음을 말하고 싶었던 것 같습니다!

 FLUX

이러한 시스템을 FLUX라고 부르기로 했습니다. 왜냐하면 그들이 생각하기에 FLUX 가 가장 핵심적인 아이디어는 흐름 이었기 때문 라틴어로써의 흐름 을 뜻하는 FLUX 를 가지고 왔지 않나 싶습니다 .
위의 다이어그램에서 Dispatcher 라는 것이 추가로 있는데, 흐름을 제어하는 역할을 담당합니다.
traffic controller 라고 볼 수 있으며 발생하는 모든 이벤트들(Action) 을 제어합니다. 아까 말했다시피 순서를 지키는 것이 매우 중요하기 때문에 Store 에서 데이터를 처리하기 전까지 다른 Action 이 추가적으로 요청되더라도 저장하고 순차적으로 처리 될 수 있게 돕는 역할을 합니다.
FLUX 라는 시스템은 다음과 같은 변화를 만들어 냈습니다.
1.
향상된 데이터 일관성
2.
버그의 근원을 발견하는 것이 쉬워짐
3.
더욱 의미 있는 유닛 테스트
1번과 2번은 이미 살펴보았습니다만, 3 번은 무슨 의미일까요?
데이터가 일관성을 유지하게 되면서 당연하게도 결과를 예측하는 것이 훨씬 쉬워졌습니다. 상태와 상태를 업데이트하는 로직이 한 곳에 생겼고 그 뜻은 의존성이 줄었다는 의미이기도 하기 때문에 이러한 점도 유닛 테스트 작성에 도움이 되었습니다.
그런데 일관성을 유지하기 위해 내부로 제어권을 이동 시켰던 것을 기억하시나요? 이를 통해 저장소 사이에 의존성이 사라졌었습니다. 이는 꽤 큰 변경점을 시사합니다. 바로 코드의 작성 방법입니다.
기본적으로 의존성이 생긴다는 것은 기능이 변경 할 시에 여파 범위가 넓어진다는 것을 의미합니다.
MVC 는 이러한 여파를 해결하기 위해서 OOP(객체 지향 프로그래밍) 을 기본적인 전략으로 사용합니다.
간단하게 설명하면 모든 값들을 상대적으로 만들어서 기능 간의 의존성을 약하게 만드는 전략을 사용합니다.
아주 짧게만 OOP에 대해 이야기 하겠습니다. O v O ;;
예를 들어 printHelloMessage 라는 함수가 있다고 가정합시다. 이 함수는 defaultMessage 이라는 데이터와 console.log 라는 뷰에 의존성을 가지고 있다고 합시다.
function printHelloMessage(name) { const defaultMessage = "Hello!"; console.log(defaultMessage + name); }
JavaScript
복사
이 때 기본 메시지가 변경된다면 어떻게 해야 할까요? 혹은 더 이상 서버에서 터미널에 출력하는 것이 아니라 브라우저에 DOM으로 출력되야 한다면요? 때문에 OOP 는 모든 값들을 상대적인 참조 값으로 만들어 버립니다.
printHelloMessage(name) { const result = this.Message.buildMessage(name); this.View.print(result); }
JavaScript
복사
이제 printHelloMessage 는 메시지와 뷰에 변경 사항이 있더라도 buildMessage 만 실행하고 print 만 실행하면 됩니다. 혹은 메시지나 뷰가 아니여도 상관없습니다. 그저 필요한 메서드를 가진 무언가가 존재하기만 하면 될 뿐이죠.
하지만 FLUX 시스템 내에서 것은 이러한 의존성은 일체 없는 것입니다. 즉, 위와 같이 작성할 필요가 전혀 없다는 것이죠. 즉 FLUX에서는 적어도 객체 가 아닌 함수로 작성해도 무관하지 않을까요?

 마치며

실제로 React 혹은 Flux 에서 추구하는 가치관은 함수입니다. 의존성이 없어진 만큼 객체 지향이 가지고 있는 트레이드 오프를 지불할 필요가 없었고 때문에 함수 가 가진 있는 장점을 활용하는 것이 더 좋아 보였던 게 아닐까요? 그리고 이러한 점이 리액트 생태계 내에서 순수함수, 불변성 과 같은 절대적인 값을 활용하려는 이용하려는 하나의 이유가 된 게 아닐까요? 우리가 리액트 파트에서 봤던 멱등성 과 같은 개념도 함수 와 밀접한 영향이 있는 것 또한 우연일까요? 물론 모든 이유가 그렇다고는 할 수 없겠지만 적어도 이번 글에서 동일한 입력이라면 항상 동일한 결과를 가지는 기능이 가지는 파괴력은 충분히 느꼈으리라 생각합니다.
다만 이러한 방식은 이는 온전히 페이스북 팀의 가치관 입니다. 정답도 아닐 뿐더러 함수가 가진 불편함을 호소하는 분 역시 많습니다. 오늘은 그저 함수 를 사용하는게 어떤 사고방식을 가졌는지 잠깐 살펴본 정도입니다. 그러니 “함수형 프로그래밍 이 최고야!” 라고 말하는 것도 어불성설입니다.
이 글이 여러분에 시야를 좁게 만드는 것을 원하지 않습니다. 단지 그들의 고민이 무엇이었는지 또 해결 하기 까지 어떠한 사고를 거쳤는지 공감하고 때로는 비판적으로 고민해보시길 바랍니다.
‘탁 트인 시야’