2025-09-23 20:29
-
Flux는 복잡한 웹 애플리케이션의 데이터 흐름을 예측 가능하게 관리하기 위해 페이스북이 제안한 아키텍처다.
-
액션, 디스패처, 스토어, 뷰라는 4가지 주요 구성 요소가 단방향으로 데이터를 처리하여 상태 관리를 용이하게 하고 버그 추적을 쉽게 만든다.
-
양방향 데이터 바인딩과 MVC 패턴의 복잡성을 해결하기 위해 등장했으며, Redux와 같은 현대 프런트엔드 상태 관리 라이브러리의 사상적 기반이 되었다.
페이스북이 선택한 데이터 흐름 Flux 아키텍처 완벽 정복 가이드
복잡하게 얽힌 실타래 같은 코드, 데이터가 어디서 어떻게 변하는지 추적하다 길을 잃어버린 경험이 있는가? 특히 애플리케이션 규모가 커질수록 데이터의 흐름을 예측하고 제어하는 것은 개발의 성패를 가르는 중요한 과제가 된다. 페이스북 역시 이 문제에 직면했고, 그 해결책으로 Flux라는 새로운 아키텍처를 세상에 내놓았다.
Flux는 단순히 새로운 프레임워크나 라이브러리가 아니다. 그것은 애플리케이션의 데이터를 **‘예측 가능한 단방향’**으로 흐르게 만드는 디자인 패턴이자 사상이다. 이 핸드북에서는 Flux가 왜 탄생했는지부터 그 구조와 실제 사용법, 그리고 MVC 패턴과의 차이점까지, Flux 아키텍처의 모든 것을 깊이 있게 파헤쳐 본다.
1. Flux의 탄생 배경 혼돈 속에서 피어난 질서
Flux의 등장을 이해하려면 먼저 그것이 해결하고자 했던 문제, 즉 MVC(Model-View-Controller) 패턴의 한계를 알아야 한다. MVC는 오랜 기간 애플리케이션을 구조화하는 표준적인 방법으로 사용되어 왔다. 하지만 페이스북과 같은 거대하고 복잡한 애플리케이션에서는 MVC의 유연성이 오히려 독이 되기 시작했다.
페이스북의 ‘좀비 알림’ 버그
페이스북 개발팀은 한때 고질적인 버그에 시달렸다. 사용자가 사이트 상단의 알림 아이콘을 클릭해 모든 알림을 확인했는데도, 잠시 후 아이콘에 다시 읽지 않은 알림이 있다는 표시가 뜨는 문제였다. 개발자들은 이 버그를 잡기 위해 코드를 수정했지만, 잠시 해결된 듯하다가도 다른 곳에서 똑같은 문제가 재발했다.
원인은 MVC 패턴의 **양방향 데이터 흐름(Bidirectional Data Flow)**에 있었다.
MVC에서 모델(Model)은 뷰(View)를 업데이트하고, 뷰는 다시 모델을 업데이트할 수 있다. 컨트롤러(Controller)는 이 둘 사이의 상호작용을 관리한다. 작은 애플리케이션에서는 이 구조가 효율적이지만, 기능이 많아지고 여러 모델과 뷰가 서로 얽히기 시작하면 데이터의 흐름은 걷잡을 수 없이 복잡해진다. 하나의 변경이 마치 나비효과처럼 예기치 않은 연쇄 반응을 일으켜 시스템의 상태를 예측 불가능하게 만드는 것이다. 페이스북의 알림 버그는 바로 이 ‘예측 불가능성’ 때문에 발생한 참사였다.
해결책 단방향 데이터 흐름
이 문제를 해결하기 위해 페이스북은 데이터 흐름을 한쪽 방향으로만 흐르도록 강제하는 새로운 구조를 고안했다. 이것이 바로 **Flux의 핵심인 단방향 데이터 흐름(Unidirectional Data Flow)**이다.
마치 복잡한 양방향 도로를 일방통행 도로로 정리하는 것과 같다. 데이터의 출발지와 목적지가 명확해지고, 흐름의 경로가 단 하나로 고정되면서 전체 시스템의 상태 변화를 훨씬 쉽게 추적하고 예측할 수 있게 되었다. 버그가 발생하더라도 데이터의 흐름을 따라가기만 하면 원인을 찾는 것이 훨씬 수월해진다.
2. Flux의 핵심 구조 4가지 구성 요소
Flux 아키텍처는 네 가지 핵심적인 구성 요소로 이루어져 있으며, 이들은 정해진 규칙에 따라 상호작용하며 데이터를 한 방향으로 흐르게 한다. 각 구성 요소를 도시의 교통 시스템에 비유하여 이해해 보자.
| 구성 요소 | 역할 | 비유 |
|---|---|---|
| 액션 (Action) | 애플리케이션에서 발생하는 모든 변화에 대한 정보 객체 | 교통 정보 (예: ‘A 지점에서 사고 발생’) |
| 디스패처 (Dispatcher) | 모든 액션을 받아 스토어로 전달하는 중앙 허브 | 중앙 교통 관제소 |
| 스토어 (Store) | 애플리케이션의 상태와 비즈니스 로직을 관리 | 특정 구역의 교통 상황 데이터베이스 |
| 뷰 (View) | 사용자에게 보여지는 UI, 스토어의 데이터를 시각화 | 도로 위 전광판 |
2.1 액션 (Action) & 액션 생성자 (Action Creator)
액션은 애플리케이션에서 “무슨 일이 일어났는가”를 설명하는 아주 간단한 자바스크립트 객체다. 가장 중요한 속성은 type으로, 어떤 종류의 액션인지를 명확하게 나타내는 문자열 상수다. 그 외에 변화에 필요한 데이터(페이로드, payload)를 함께 담는다.
JavaScript
// 사용자가 새로운 할 일을 추가했을 때의 액션
{
type: 'ADD_TODO',
text: 'Flux 아키텍처 공부하기'
}
// 특정 할 일을 완료 처리했을 때의 액션
{
type: 'COMPLETE_TODO',
id: 123
}
이러한 액션 객체를 직접 만들기보다는, **액션 생성자(Action Creator)**라는 헬퍼 함수를 통해 만드는 것이 일반적이다. 액션 생성자는 필요한 데이터를 인자로 받아 완전한 액션 객체를 생성하고, 이 액션을 디스패처로 보내는 역할까지 담당한다.
JavaScript
// 액션 생성자 함수
function addTodo(text) {
const action = {
type: 'ADD_TODO',
text: text
};
// 생성된 액션을 디스패처로 전달한다.
AppDispatcher.dispatch(action);
}
2.2 디스패처 (Dispatcher)
디스패처는 Flux 아키텍처의 중앙 교통 관제소다. 애플리케이션 전체에 단 하나만 존재하는 싱글톤(Singleton) 객체이며, 모든 데이터 흐름의 중심에 위치한다.
디스패처의 역할은 매우 단순하고 명확하다.
-
액션을 받는다: 액션 생성자로부터 액션을 전달받는 유일한 통로다.
-
모든 스토어에 전파한다: 자신에게 등록된 모든 스토어(Store)에게 액션을 빠짐없이 전달(broadcast)한다.
디스패처는 액션에 담긴 type이나 payload를 해석하지 않는다. 그저 우체부가 편지 내용을 보지 않고 주소지로 배달만 하는 것처럼, 들어온 액션을 모든 스토어에 그대로 전달할 뿐이다. 이 단순함이 Flux의 예측 가능성을 보장하는 핵심 원리 중 하나다.
또한, 디스패처는 스토어 간의 의존성을 관리하는 waitFor()와 같은 중요한 메서드를 제공한다. 예를 들어, 특정 스토어의 업데이트가 다른 스토어의 업데이트 이후에 일어나야 할 경우, waitFor()를 사용해 처리 순서를 보장할 수 있다.
2.3 스토어 (Store)
스토어는 애플리케이션의 상태(State)와 관련 비즈니스 로직을 담고 있는 컨테이너다. MVC의 모델과 유사해 보이지만, 결정적인 차이가 있다. 스토어는 외부에서 직접 데이터를 변경할 수 있는 세터(setter) 메서드를 가지지 않는다.
스토어의 상태를 변경할 수 있는 유일한 방법은 디스패처가 전달해 준 액션에 반응하는 것뿐이다.
스토어의 주요 역할은 다음과 같다.
-
디스패처에 자신을 등록한다: 애플리케이션이 시작될 때, 스토어는 디스패처에 콜백 함수를 등록하여 액션을 받을 준비를 한다.
-
액션을 처리하여 상태를 변경한다: 디스패처로부터 액션을 받으면,
switch문 등을 사용해 액션의type을 확인하고, 그에 맞는 로직을 실행하여 내부 상태를 변경한다. -
‘변경 이벤트(Change Event)‘를 발생시킨다: 상태가 변경된 후에는, “내 데이터가 바뀌었으니 필요한 사람은 가져다 쓰세요!”라는 의미의 변경 이벤트를 외부에 알린다.
스토어는 특정 도메인에 대한 모든 데이터를 관리한다. 예를 들어, ‘할 일 목록’ 애플리케이션이라면 TodoStore가, ‘사용자 정보’라면 UserStore가 존재할 수 있다.
2.4 뷰 (View)
뷰는 사용자에게 보여지는 UI 컴포넌트다. React 컴포넌트가 이 역할을 수행하는 경우가 가장 일반적이다. 뷰는 Flux 흐름에서 데이터 순환의 시작점이자 종착점이다.
뷰의 역할은 두 가지로 나뉜다.
-
스토어의 데이터를 화면에 렌더링한다: 뷰는 관심 있는 스토어를 구독(subscribe)하고 있다가, 스토어가 발생시키는 ‘변경 이벤트’를 감지한다. 이벤트가 발생하면, 뷰는 스토어에 최신 데이터를 요청하여 자신의 상태를 업데이트하고 UI를 다시 렌더링한다.
-
사용자 상호작용에 따라 액션을 발생시킨다: 사용자가 버튼을 클릭하거나 폼에 텍스트를 입력하는 등의 행동을 하면, 뷰는 해당 정보를 담아 액션 생성자를 호출한다. 이로써 새로운 데이터 흐름 사이클이 시작된다.
이러한 구조 때문에 뷰는 애플리케이션의 상태가 어떻게 구성되어 있는지, 비즈니스 로직이 어떻게 동작하는지 전혀 알 필요가 없다. 오직 스토어로부터 데이터를 받아와 화면에 그리고, 사용자 입력이 있으면 액션을 날려주는 역할만 충실히 수행하면 된다.
3. Flux의 작동 방식 데이터 흐름의 여정
이제 네 가지 구성 요소가 어떻게 협력하여 단방향 데이터 흐름을 완성하는지, ‘할 일 추가’ 시나리오를 통해 전체 과정을 따라가 보자.
시나리오: 사용자가 ‘Flux 공부하기’라는 할 일을 입력하고 ‘추가’ 버튼을 클릭한다.
-
(뷰) 사용자 입력 및 액션 생성:
-
사용자가 입력 필드에 ‘Flux 공부하기’를 입력하고 ‘추가’ 버튼을 클릭한다.
-
버튼의 클릭 이벤트 핸들러는
addTodo('Flux 공부하기')와 같은 액션 생성자 함수를 호출한다.
-
-
(액션 생성자 → 디스패처) 액션 생성 및 디스패치:
-
addTodo액션 생성자는{ type: 'ADD_TODO', text: 'Flux 공부하기' }라는 액션 객체를 생성한다. -
생성된 액션을 디스패처의
dispatch()메서드에 인자로 넘겨 호출한다.AppDispatcher.dispatch({ type: 'ADD_TODO', text: 'Flux 공부하기' });
-
-
(디스패처 → 스토어) 액션 전파:
- 디스패처는 자신에게 등록된 모든 스토어에게 이 액션을 전달한다. 이 시나리오에서는
TodoStore가 이 액션을 받게 될 것이다.
- 디스패처는 자신에게 등록된 모든 스토어에게 이 액션을 전달한다. 이 시나리오에서는
-
(스토어) 상태 업데이트 및 이벤트 발생:
-
TodoStore는 전달받은 액션의type이 ‘ADD_TODO’인 것을 확인한다. -
자신이 관리하는 할 일 목록(todos) 배열에
{ id: 1, text: 'Flux 공부하기', completed: false }와 같은 새로운 할 일 객체를 추가하여 내부 상태를 업데이트한다. -
상태 변경이 완료되면,
TodoStore는emitChange()와 같은 메서드를 호출하여 ‘변경 이벤트’를 발생시킨다.
-
-
(스토어 → 뷰) 뷰 업데이트:
-
TodoStore의 변경 이벤트를 구독하고 있던 뷰(할 일 목록 컴포넌트)가 이 이벤트를 감지한다. -
뷰는
TodoStore.getTodos()와 같은 메서드를 호출하여 최신 할 일 목록 데이터를 가져온다. -
가져온 최신 데이터를 기반으로 자신의 상태를 업데이트하고
render()함수를 호출하여 UI를 다시 그린다. 화면에 ‘Flux 공부하기’라는 새로운 할 일이 나타난다.
-
이것으로 하나의 데이터 흐름 사이클이 완결되었다. 모든 과정은 액션 → 디스패처 → 스토어 → 뷰라는 엄격한 단방향 경로를 따라 순서대로 진행되었다. 어디에서도 데이터가 역류하거나 옆길로 새는 경우는 없다. 이것이 바로 Flux가 보장하는 예측 가능성이다.
4. 심화 탐구 Flux vs MVC 그리고 그 이후
Flux는 MVC의 대안으로 등장했지만, 정확히는 MVC를 완전히 대체하는 개념이라기보다는 C(Controller)와 M(Model) 부분을 재해석한 것에 가깝다.
Flux와 MVC의 결정적 차이
| 관점 | Flux | MVC (전통적인) |
|---|---|---|
| 데이터 흐름 | 단방향 (Unidirectional). 예측 가능하고 추적이 용이하다. | 양방향 (Bidirectional). 모델과 뷰가 서로를 직접 업데이트할 수 있어 복잡성이 증가한다. |
| 데이터 처리 | 모든 데이터 변경은 디스패처를 통과하는 액션으로 시작된다. | 컨트롤러가 모델의 데이터를 조작하고, 뷰가 이 변경을 반영하거나 직접 모델을 변경한다. |
| 의존성 | 컴포넌트 간 의존성이 낮다. 뷰는 스토어에만 의존한다. | 모델, 뷰, 컨트롤러가 서로 복잡하게 얽히기 쉽다. (Spaghetti Code) |
| 디버깅 | 데이터 흐름이 명확하여 특정 액션이 어떤 상태 변화를 일으켰는지 추적하기 쉽다. | 상태 변화의 원인을 찾기 위해 여러 컴포넌트의 상호작용을 모두 확인해야 할 수 있다. |
MVC가 유연한 양방향 도로망이라면, Flux는 엄격한 규칙을 가진 중앙 관리형 일방통행 시스템이다. 작은 마을에서는 양방향 도로가 편리할 수 있지만, 거대한 대도시에서는 체계적인 일방통행 시스템이 교통 혼잡을 막고 예측 가능성을 높이는 것과 같은 이치다.
Flux는 패턴일 뿐, Redux의 등장
중요한 것은 Flux는 라이브러리나 프레임워크가 아닌 아키텍처 패턴이라는 점이다. 페이스북은 Flux의 개념과 이를 구현하기 위한 기본 Dispatcher 라이브러리만 제공했다. 이 때문에 개발자들은 스토어나 액션 등을 직접 구현해야 했다.
이러한 상황에서 Flux의 사상을 계승하면서도 몇 가지 강력한 제약을 추가하여 편의성과 예측 가능성을 극대화한 라이브러리가 등장하는데, 그것이 바로 Redux다.
Redux는 Flux와 유사한 단방향 데이터 흐름을 따르지만, 다음과 같은 차이점을 가진다.
-
단일 스토어 (Single Store): Redux는 애플리케이션 전체에 단 하나의 스토어만 사용한다. 모든 상태가 하나의 거대한 객체 트리로 관리되어 상태를 파악하기 더욱 쉽다.
-
순수 함수로서의 리듀서 (Reducer): 스토어의 상태 변경 로직을 ‘리듀서’라는 순수 함수(Pure Function)가 담당한다. 리듀서는 이전 상태와 액션을 받아 새로운 상태를 반환하며, 기존 상태를 직접 변경하지 않는다(불변성).
-
개념의 단순화: 디스패처라는 개념이 라이브러리 내부에 통합되어 개발자가 직접 다룰 필요가 없어졌다.
Redux의 등장은 프런트엔드 상태 관리의 패러다임을 바꾸었고, 오늘날 많은 React 애플리케이션에서 사실상의 표준으로 자리 잡게 되었다. 하지만 그 근간에는 Flux의 단방향 데이터 흐름이라는 위대한 아이디어가 자리 잡고 있다는 사실을 잊어서는 안 된다.
5. 결론 Flux가 남긴 유산
Flux 아키텍처는 복잡한 웹 애플리케이션의 상태 관리에 대한 명쾌한 해답을 제시했다. 비록 지금은 Redux, MobX, Zustand 등 더 발전되고 사용하기 편리한 상태 관리 라이브러리들에게 주도권을 넘겨주었지만, Flux가 제시한 **‘단방향 데이터 흐름’**이라는 핵심 원칙은 현대 프런트엔드 개발의 근간을 이루는 중요한 사상으로 남아있다.
Flux를 이해하는 것은 단순히 과거의 기술을 배우는 것이 아니다. Redux를 비롯한 현대 상태 관리 라이브러리들이 왜 그렇게 설계되었는지 그 근본적인 이유를 이해하고, 예측 가능하고 유지보수하기 쉬운 코드를 작성하는 철학을 배우는 과정이다. 복잡한 데이터 흐름 속에서 길을 잃었다면, Flux가 제시한 명확한 이정표를 다시 한번 떠올려보자. 그 안에 문제 해결의 열쇠가 있을 것이다.