처음 팀에 왔을 때 데이터베이스 동기화 모니터링 솔루션을 개발하는 프로젝트의 웹 어플리케이션 개발에 참여하게 되었습니다. 저희가 사용할 라이브러리는 React로 이미 결정이 되어 있었고, 추가적으로 Mobx라는 상태관리 라이브러리를 사용하기로 했습니다. 이 전에 저는 Vue 프레임워크를 사용하여 상태관리 라이브러리로 Vuex를 사용하였는데, Vuex에 대해서 공부할 때 Vuex와 Redux를 비교하는 글이 많아 Redux에 대해서는 조금이나마 알고 있었지만 Mobx는 처음 접해보았습니다.
그래서 프로젝트에 참여하기 앞서 Mobx에 대해서 공부를 했고, 이 과정에서 Mobx와 Redux는 무슨 차이가 있는지 그리고 왜 우리 팀은 Mobx를 선택하게 되었는지에 대해서 알고자 두 상태 관리 라이브러리에 대해서 공부했고, 그 내용을 글로 남기고자 합니다.
Mobx와 Redux에 대해서 알아보기 전 이 두 라이브러리(Vuex를 포함하여)의 기원에 대해서 먼저 알아보는 것이 좋을 것 같습니다. Flux는 페이스북 개발자들이 처음 고안한 개념으로 페이스북 채팅 시스템에서 발견된 결함을 해결하기 위해 태어났습니다.
해당 결함에 대해서 설명하자면, 애플리케이션의 데이터를 공유하는 구성 요소가 많아질수록, 그리고 그 구성 요소간의 상호작용이 복잡해지면 복잡해질수록, 데이터의 상태를 예측하거나 이해하기 어려워집니다. 그렇게 되면 애플리케이션을 더 이상 확장하거나 유지 보수하기 어려운 상황이 발생할 수 있습니다.
Flux는 이러한 결함을 완화시키기 위한 아키텍처 가이드라인으로 Mobx와 Redux 그리고 Vuex는 Flux의 일부 패턴을 구현한 라이브러리라고 할 수 있습니다.
Flux는 기본적으로 스토어(store), 액션(action), 디스패처(dispatcher), 뷰(View) 네 부분으로 구성됩니다.
Flux에서 컴포넌트는 데이터와 완전히 분리되어야 하지만, 데이터가 변경될 때마다 다시 렌더링할 수 있도록 알림을 받습니다. 스토어는 애플리케이션의 모든 상태(데이터와 UI)를 유지하며 상태가 변경되면 이벤트를 발송합니다. 컴포넌트는 필요한 데이터를 포함하는 스토어를 구독하며 데이터가 변경되었다는 알림을 받으면 자신을 다시 렌더링합니다.
애플리케이션의 다른 부분에서는 스토어의 데이터를 변경, 갱신, 삽입할 수 없다는 사실입니다. 오직 getter형태로 데이터에 대한 접근권한이 주어지며 데이터를 변경하려면 다른 수단이 필요합니다.
위에서 말한 수단이 바로 액션입니다. 액션은 애플리케이션의 거의 모든 부분에서 생성될 수 있으며, 사용자 상호작용(버튼 클릭, 검색 결과 요청, 텍스트 입력)과 Ajax 요청, 타이머, 웹 소켓 이벤트 등을 통해서 생성됩니다. 모든 액션은 타입(고유한 이름)과 선택적인 페이로드를 포함합니다. 스토어는 액션이 발송되면 자신의 데이터를 업데이트합니다.
디스패처는 액션을 스토어로 전달하는 과정을 조율하고 스토어의 액션 핸들러가 올바른 순서로 실행되도록 관리합니다. 디스패처는 액션들을 받아서 해당 해당 디스패처를 등록한 스토어에게 전달하는 역할을 하며 한 애플리케이션은 싱글턴 디스패처를 가지고 있어야 합니다.
애플리케이션이 커지면서 Store끼리 의존성이 생길 수 있습니다. 만약에 Store A가 Store B가 업데이트가 된 이후에 뭔가를 처리해야 하는 경우가 있다면, 이런 경우에 디스패처가 waitFor()같은 메서드를 통해 중재를 해줍니다.
스토어의 데이터는 뷰를 통해서 사용자에게 보여집니다. 뷰가 스토어의 데이터를 사용할 때는 스토어의 change 이벤트도 구독해야 하며, 스토어가 이벤트를 발행할 때 뷰는 새로운 데이터를 받아 자신을 다시 렌더링합니다.
액션들은 주로 사용자가 뷰와 상호작용할 때 뷰로부터 생성됩니다.
Single Source of truth
애플리케이션의 상태는 오직 한가지의 스토어(저장공간) 안에 object tree(객체트리)에 저장됩니다. 하나의 객체 트리는 어플리케이션을 디버깅 하거나 검사하는 것을 쉽게 만들어 줍니다.
State is read-only
스토어 안에 저장된 상태는 무슨 일이 일어나는지를 정의하는 객체인 action에 의해서만 변경됩니다.
즉, 스토어 안의 상태는 오직 읽기 전용이며, 스토어 안의 상태를 추가/수정/삭제하려면 액션을 발행해야만 합니다.
모든 변화들이 하나의 스토어로 집중화 되어있고, 오직 한가지의 엄격한 순서(단방향)에 의해 일어나기 때문에, 알아차리기 어려운 미묘한 race condition이 없습니다. 또한 액션들이 평범한 객체이기 때문에, 기록되고, 직렬화되고, 저장되고 그리고 나중에 디버깅이나 테스트 목적으로 재사용될 수 있습니다.
Changes are made with pure functions
상태 트리가 action에 의해서 변환되는 방법을 지정하기 위해서 pure reducers를 작성해야 합니다.
Reducer 함수는 순수함수로써 이전의 상태와 액션을 인자로 받고, 변화된 상태를 반환합니다. 기억해야 하는 것은, reducer가 이 전의 상태를 변경하지 않고 새로운 상태를 반환한다는 점입니다.
Store
스토어는 애플리케이션의 상태를 저장하는 공간입니다. Redux에서 스토어는 오직 하나만 존재합니다.
Action
스토어의 state를 변경하기 위한 이벤트에 대한 정의입니다.
Reducer
action을 받고 action이 담고 있는 값과 타입을 이용해서 실제로 스토어의 state를 변경합니다. 상태를 변경시키는게 아니라 기존의 상태와 action객체를 참고하여 새로운 state를 반환합니다.
Dispatch
action을 reducer에 전달합니다.
애플리케이션의 상태로 부터 파생될수 있는 모든것들은 파생되어야한다.
Store
Mobx의 스토어는 Redux와 다르게 여러 개가 될 수 있습니다. 예를 들어서 유저와 프로젝트의 개념을 가지고 있는 웹페이지가 있을 때, Mobx를 사용한다면 유저의 정보를 담고 있는 UserStore와 ProjectStore를 가질 수 있습니다.
Repository
Repository는 Ajax로 서버로부터 데이터를 가져오는 부분입니다. Mobx의 구조는 spring의 그것과 매우 흡사한데, store가 spring의 service에 해당한다면, repository는 spring의 repository와 비슷한 역할을 한다고 할 수 있습니다.
Decorator
Decorator는 Mobx의 구조를 이루는 것 중 하나라기보다는 일종의 문법적 설탕입니다. Decorator를 사용하여 적은 양의 코드로 store를 컴포넌트에 주입할 수 있고, 감시할 대상 state를 설정할 수도 있습니다.
함수형 컴포넌트를 사용할 경우에는 inject 데코레이터를 사용하지 않고, inject로 컴포넌트를 한번 감싸줌으로써 컴포넌트 내부에서 스토어의 값에 접근할 수 있습니다.
상태 관리 라이브러리는 어느 정도 규모가 있는 프로젝트는 필수에 가까운 요소라고 생각합니다. 컴포넌트가 많아질수록, props를 통해서 전달해야 하는 경로가 복잡해지는데 이는 유지보수성을 저해하는 요소라고 생각합니다. 또한 상태 관리 라이브러리를 사용하여 복잡한 상태 업데이트 로직을 컴포넌트와 분리하고 모듈화하는 것은 협업에 큰 도움을 줍니다.
현재 저희 팀은 Mobx를 사용하고 있습니다. Mobx를 선택한 이유를 몇 가지 꼽아보자면, 저희 팀의 주된 업무는 웹개발이 아니라는 점이 가장 큽니다. Redux는 Mobx에 비해 러닝 커브가 높고 보일러 플레이트 코드의 양 또한 만만치 않습니다. 또한 저희가 개발중인 웹앱이 간단한 페이지로 구성되어 있기 때문에 Mobx가 저희 프로젝트에 더 적합하다고 판단했습니다.
프로젝트의 규모가 클수록 Mobx보다 Redux를 선택하는 경향이 크다고 합니다. 이유로는 여러가지가 있겠지만, 몇가지를 정리해보겠습니다.
스토어가 여러개라는 점이 오히려 독이 될 수도 있습니다. 애플리케이션이 여러 상태를 변경했을 때 다수의 스토어가 영향을 받을 수 있는데, 이는 예측할 수 없는 결과를 가져올 수 있습니다.
Mobx는 Redux와 다르게 스토어의 데이터를 액션의 발행없이 업데이트할 수 있습니다. 이는 개발자가 구현하기에는 용이하지만 테스트나 유지보수를 할 때 예측할 수 없는 결과를 가져올 수 있습니다.
Redux의 경직된 접근방식(불변성, action을 통한 상태 반환)은 확장성 및 디버깅에 있어 강점을 가지고 있습니다. Redux의 예측가능한 점이 유지보수와 테스트 측면에서 강점을 가져다 줍니다.\
Mobx에 대한 테스트 및 디버깅을 위한 효율적인 도구가 부족합니다. 프로젝트의 규모가 커질수록 테스트 코드의 중요성이 커지는데 이는 큰 단점이 될 수 있습니다.
Mobx가 빠르게 성장하고 있지만, 여전히 Redux를 사용하고 있는 팀 또는 회사가 훨씬 많고 커뮤니티의 크기 또한 비교하기 어렵습니다. 즉, 문제에 맞닥뜨렸을 때 도움을 요청하기 쉬운 쪽은 Redux입니다.
간단히 정리하자면, 장기 프로젝트 또는 오랜 기간 유지보수가 필요하며 확장성을 고려해야하는 프로젝트에 대해서는 Redux가 더 적합합니다. 이와 반대로 간단한 웹앱에서 빠르게 개발이 필요한 경우에는 Mobx가 더 나은 선택이 될 수 있을 것 같습니다.
우아한 형제들 React에서 Mobx경험기 https://woowabros.github.io/experience/2019/01/02/kimcj-react-mobx.html
https://itsnothingg.medium.com/%EB%8F%84%EB%8C%80%EC%B2%B4-vuex%EA%B0%80-%EB%AD%90%EA%B8%B8%EB%9E%98-ad5bc15f2371
https://sungjk.github.io/2016/10/03/Whatisflux.html
https://hannut91.github.io/blogs/flux
https://redux.js.org/understanding/thinking-in-redux/three-principles
https://blog.rhostem.com/posts/2019-07-22-mobx-v6-and-react-v16-8