데이터 흐름의 혁신 - 플럭스(Flux) 아키텍처의 태동

18

발행일: 2025년 05월 01일

인스타그램 웹 버전의 핵심, 피드 구현 성공. 리액트 팀의 사기는 하늘을 찔렀다. 가상 DOM의 마법과 컴포넌트 기반 개발의 우아함은 기존 방식의 고통을 잊게 해주는 듯했다. 마치 잘 닦인 고속도로 위에 올라탄 기분이었다. 하지만 그 고속도로 위를 달리는 차들이 점점 많아지고, 예상치 못한 곳에서 서로 얽히기 시작하면서 새로운 문제가 스멀스멀 피어오르기 시작했다.

"대체 왜 '좋아요' 개수가 업데이트 안 되는 거야? 분명 서버 응답은 제대로 왔는데!"
"이 댓글을 삭제했는데, 왜 다른 컴포넌트에서는 아직도 보이지?"
"사용자 프로필 편집 내용을 저장했는데, 페이지 헤더의 이름은 그대로네?"

애플리케이션의 규모가 커지고 기능이 복잡해지면서, 리액트의 컴포넌트만으로는 해결되지 않는 문제가 발생하기 시작했다. 바로 '데이터 상태 관리' 의 문제였다.

리액트는 UI를 어떻게 그릴지에 대한 혁신적인 해답을 제시했지만, 애플리케이션 전체의 데이터(상태)를 어디서, 어떻게 관리하고 컴포넌트 간에 공유할지에 대해서는 명확한 규약을 제시하지 않았다.

초기에는 간단했다. 부모 컴포넌트가 상태를 가지고, 자식 컴포넌트에게 속성(props)으로 데이터를 내려주면 되었다. 하지만 애플리케이션 구조가 깊어지고 복잡해지면서, 이런 방식은 한계에 부딪혔다.

  • Props Drilling: 상태가 필요한 컴포넌트가 저 아래 깊숙이 있는데, 중간의 모든 컴포넌트들이 그 상태를 오직 전달만을 위해 줄줄이 넘겨줘야 하는 비효율적인 상황 발생.
  • 상태 끌어올리기 (Lifting State Up): 여러 자식 컴포넌트가 공유해야 하는 상태는 공통의 상위 컴포넌트로 계속 끌어올려야 했다. 결국 최상위 컴포넌트가 너무 많은 상태를 관리하게 되어 비대해지는 문제.
  • 예측 불가능한 부작용: 어떤 컴포넌트가 다른 컴포넌트의 상태를 변경하기 위해 부모에게 받은 콜백 함수를 여러 단계 거슬러 호출하거나, 심지어는 전역 이벤트 버스 같은 것을 사용하면서 데이터의 흐름이 어디서 시작되어 어디로 흘러가는지 추적하기 어려워졌다.

"이거… 뭔가 잘못되고 있어."

조던 워크는 다시 깊은 고민에 빠졌다. 리액트로 스파게티 '뷰(View)' 코드는 해결했지만, 이제는 스파게티 '데이터 흐름(Data Flow)'이라는 새로운 망령이 어른거리는 듯했다. 특히 인스타그램처럼 여러 컴포넌트가 동일한 데이터(예: 게시물의 좋아요 상태, 댓글 목록)에 의존하고, 사용자의 행동 하나(예: 좋아요 클릭)가 여러 곳의 UI에 동시에 영향을 줘야 하는 상황에서는 더욱 심각했다.

'UI는 상태의 함수다. 좋아. 그런데 그 상태 자체는 어디서 어떻게 관리되어야 하는 거지? 마치… 혈액이 온몸을 돌아야 하는데, 혈관이 여기저기 꼬이고 막혀 있는 느낌이야.'

그는 MVC 패턴의 실패를 떠올렸다. 모델과 뷰가 직접 얽히거나, 컨트롤러가 비대해지면서 발생했던 혼돈. 양방향 데이터 바인딩이 가져왔던 예측 불가능성. 리액트는 뷰 레이어의 혁신을 가져왔지만, 애플리케이션 아키텍처 전체의 관점에서 보면 여전히 '데이터 관리'라는 숙제가 남아 있었다.

"데이터는… 예측 가능하게 흘러야 해. 마치… 일방통행 도로처럼."

그의 머릿속에 다시 한번 '단방향(Unidirectional)'이라는 키워드가 떠올랐다. 리액트 컴포넌트 자체도 부모에서 자식으로 속성(props)이 흘러가는 단방향 구조를 가지고 있었다. 그렇다면 애플리케이션 전체의 데이터 흐름도 이런 단방향 원칙을 적용할 수는 없을까?

그때, 조던과 동료들은 새로운 아키텍처 패턴을 구상하기 시작했다. 이것은 리액트 자체의 기능은 아니었지만, 리액트와 함께 사용했을 때 시너지를 낼 수 있는, 애플리케이션 데이터 관리를 위한 '설계 사상'이었다.

  1. 액션 (Action): 애플리케이션에서 발생하는 모든 상태 변경의 '원인'을 명확하게 정의한다. 사용자가 버튼을 클릭하거나, 서버로부터 데이터를 받아오는 등의 모든 변화는 '액션' 객체로 표현된다. (예: { type: 'LIKE_POST', postId: 123 })
  2. 디스패처 (Dispatcher): 모든 액션은 반드시 이 중앙 허브, 즉 '디스패처'를 통과한다. 디스패처는 액션을 받아서 등록된 처리자들에게 전달하는 역할만 한다. 마치 교통정리를 하는 교차로처럼.
  3. 스토어 (Store): 애플리케이션의 '상태'와 그 상태를 변경하는 '로직'을 가지고 있는 저장소. 각 스토어는 디스패처에 자신을 등록하고, 관심 있는 액션이 전달되면 그에 따라 자신의 상태를 업데이트한다. 오직 스토어만이 상태를 변경할 수 있다!
  4. 뷰 (View - React Component): 스토어로부터 최신 상태를 가져와 화면에 그린다. 사용자의 행동이 발생하면, 뷰는 직접 상태를 바꾸는 대신 새로운 '액션'을 만들어 디스패처에게 보낸다.

이 구조의 핵심은 엄격한 단방향 데이터 흐름이었다.

액션 발생 (뷰) -> 디스패처 -> 스토어 (상태 변경) -> 뷰 (변경된 상태 반영)

데이터가 절대 역류하거나 옆길로 새지 않았다. 모든 상태 변경은 반드시 액션과 디스패처, 스토어를 거쳐 예측 가능한 방식으로 이루어졌다. 어떤 상태가 왜, 어떻게 변경되었는지 추적하기가 훨씬 용이해졌다. 디버깅 지옥에서 벗어날 실마리가 보이기 시작했다!

"이 흐름… 마치 강물 같군."

누군가 중얼거렸다. 그들은 이 새로운 아키텍처 패턴에 '흐름'이라는 의미를 담아 이름을 붙였다.

플럭스 (Flux).

플럭스는 프레임워크나 라이브러리가 아니었다. 그것은 리액트 애플리케이션의 복잡성을 제어하기 위한 하나의 '패턴'이자 '개념'이었다. 페이스북 팀은 이 플럭스 패턴에 기반한 내부 구현체들을 만들기 시작했고, 인스타그램 웹 프로젝트의 복잡한 상태 관리 문제에 적용하기 시작했다.

처음에는 개발자들이 새로운 개념에 적응하는 데 시간이 걸렸다. "왜 이렇게 번거롭게 해야 하죠?" 라는 불만도 나왔다. 하지만 플럭스 패턴을 적용한 부분에서 데이터 흐름이 명확해지고 버그 추적이 용이해지는 것을 경험하면서, 점차 그 가치를 인정하기 시작했다.

리액트가 뷰 레이어의 혁신이었다면, 플럭스는 그 혁신을 뒷받침하는 데이터 관리 아키텍처의 혁신이었다. 이 두 가지가 결합되었을 때, 비로소 복잡하고 거대한 웹 애플리케이션을 안정적이고 확장 가능하게 만들 수 있는 강력한 기반이 마련되기 시작한 것이다.

조던 워크는 플럭스 아키텍처 다이어그램을 바라보며 희미하게 미소 지었다. 코드의 연금술은 아직 끝나지 않았다. 리액트라는 강력한 도구에 플럭스라는 정교한 설계도를 더하면서, 웹 개발의 새로운 시대는 더욱 구체적인 모습을 갖추어 가고 있었다.