본문 바로가기
프론트엔드

프론트엔드 설계 고민 -6-

by ISA(류) 2022. 10. 3.

요즘 운동에 빠져서 개발을 소홀히하고 있다. 그래서 글쓰는 것도 프로젝트 진행도 느긋하게 지연 되는데..

원래 쓰려던 내용은 UI 컴포넌트를 구성하는 법에 대한 디테일이였지만 최근에 상태관리에 대해서 어려워 하는 사람들이 많은걸 봐서 인지 상태관리에 대해서 조금 정리하는게 좋을거 같다는 생각이 들었다.

 

웹 프론트엔드에서의 상태관리 도구의 경우 여러가지가 있지만 큰 맥락은 변하지 않으므로 익숙한 react와 recoil 기준으로 생각을 간단하게 정리해보겠다.

 

웹프론트 개발자는 웹 어플리케이션을 만든다. 그런데 왜 굳이 웹 사이트를 웹앱이라고 하는 것일까? 

오늘날에 와서는 엄밀한 구분이 불가능한 편이지만 기존의 웹 사이트의 경우 정적인 웹에 가까웠다. 단순히 마크업 된 페이지를 제공해주고 사용자 액션에 따라서 페이지 전환이 이루어지거나 액션이 있을시 벡엔드에 해당 요청을 보내서 그 처리한 결과를 받아 표시해주는 그런 정도의 웹. 그러나, 기술이 발전하고 수요가 늘어남에 따라서 점점 웹은 아니 웹 프론트는그런 단순한 수준에서 벗어나서 어플리케이션 수준의 기능들을 제공 할 필요가 있어졌다.

 

그런점 때문인지 점점 프로그레시브웹이나 WASM 또는 웹뷰를 사용한 하이브리드앱, 네이티브 어플리케이션등을 대체하려는 방향으로 기술이 발전하고 있는 편이다.(스트리밍이나 크롬 OS도 그런 시도로 봐도 될듯)

그래서 웹 프론트 엔드가 웹앱을 개발한다는 게 대체 상태관리와 무슨 상관이 있다고? 라고 생각하는 사람도 있겠지만 결국 웹 프론트 엔드 상태 관리는 네이티브앱이나 벡엔드 서버(어플리케이션 서버)의 상태관리와 크게 다르지 않다는 점을 말하고 싶었다.

 

벡엔드가 데이터에 관한 전반적인 로직들을 처리한다면 프론트엔드 역시 데이터를 다루지만 보여주기 위한 데이터의 전반적인 로직을 처리한다는게 굳이 따지면 차이점일거 같다. 은행앱을 예로 들면  통장잔고가 백만원 밖에 없는데 이체API를 호출하면서 프론트에서 통잔잔고를 1억으로 처리해서 보내고 그 1억을 특정 계좌로 이체한다는 요청을 보냈는데 그걸 아무 검증 없이 처리해버리면 대형사고가 되듯이 그런 데이터의 처리의 경우 어차피 재검증이 필요하기에 그저 액션을 받아서 벡엔드가 내부의 데이터들로 처리하는게 더 알맞다.

다만 보여주기 위해서 데이터 포맷이 달라진다거나 범위를 표시해주거나 기존에 가지고 있던 데이터들로 파생되는 데이터를 만들어서 보여주는 경우라면 해당 계산들을 굳이 벡엔드 어플리케이션 서버에서 처리 할 필요 없이 프론트 엔드 영역에서 처리하는게 여러모로 좋다.

 

당연히 단순히 보여주기 위한 인터랙션의 경우 프론트엔드에서 처리해야하겠지만, readonly 데이터 역시 프론트 엔드에서 처리하는게 합리적이라고 생각 하는 편인데 이건 사람 마다 생각이 다를 수는 있을 것이라 생각하지만 내 생각은 그렇다.

그렇다면 어떻게 데이터를 관리할까? 고전적인 모놀리스 아키텍처에서  MVC 관련한 파생 패턴이 많이 유명하고 자주 쓰인다. 앱에서는 MVVM 파생 패턴이 자주 쓰이고 있다. 두가지 모두의 공통점을 꼽자면 데이터, 즉 상태를 따로 모델로 구분해서 관리한다는 점과 그 모델과 view를 연결하는 레이어를 가진다는 점이 있다.

 

리액트에서 보통 이 모델의 경우 store라는 네이밍으로 많이 쓰는 것 같다. store는 상태관리 도구에 따라서 다르지만 보통 상태에 관한 정의를 기본적으로 담당한다. 해당 상태를 호출 하는 법이나, 상태의 타입, 또는 구조 등 recoil 같은 형식으로 상태관리 구조가 변경되면서 생긴 특이점이 있다면 거기에 파생 상태 라는 개념이 추가된 정도가 있다. 불변 상태와 그 불변 상태에서 파생 되는 상태 그리고 그 상태를 처리하는 방식 등이 순수함수라는 형식으로 제공되는데 이게 데이터를 가공하는데 매우 유용하다.

 

기존에는 같은 도메인의 c라는 것을 표현 하기 위해서 a와 b를 쪼개거나 결합해야 했다면 이제는 c를 받은 후 그 c를 a와 b로 가공하거나 c나 d로 가공하는 과정 자체를 모델에서 조금 더 쉽게 처리가 가능해졌다. 데이터 스키마를 구성하는게 조금 더 용이해졌다고 볼 수 있다. 해당 과정들 자체가 스토어에서 이루어지기에 기존 처럼 a나 b를 감시하고 변경이 있을시 그에서 파생하는 상태에 대한 로직들을 컴포넌트에서 처리 할 필요가 없어졌고 또는 스토어 자체에서도 복잡하게 처리 할 필요가 없어졌다.

 

특정 state에서 파생하는 상태를 변경할때도 해당 상태와 관련된 모든 부분이 자동으로 업데이트 되는 것은 물론 데이터 스키마에 대한 부분을 굳이 model 외적인 부분에서 신경쓸 필요가 없어졌으니 관심사 분리라는 측면에서도 상당히 긍정적이라고 생각한다. 다만 해당 부분은 모두 사용자의 리소스를 활용해서 유지하기에 벡엔드와 같이 규모가 너무 크거나 또는 너무 장황할 경우 새로운 문제점이 될 수 있는 부분으로 보인다.(서버 리소스를 절약 할 수 있는 대신 불확실한 사용자 환경에 영향을 받는다는 문제점이 존재한다.)

 

store 와 모델이 벡엔드와 유사한 측면을 가지고 있다는 것을 이해한 사람이라면 어떤 방식으로 관리해야할지도 감이 어느 정도 잡혔을텐데 벡엔드의 모델을 관리하던 방식과 기본 원리가 크게 다르지 않다. 그렇다면 컨트롤러, 뷰모델 등의 view와 model간의 사이를 중개해주는 레이어의 필요성도 웹개발에 익숙한 사람이라면 크게 부정하지 않을 것이라 생각하는데 굳이 개인적으로 몇가지 이유를 꼽자면 관심사의 분리, 그리고 재사용성을 높이고 확장성을 높인다. 같은 입에 발린 말 정도를 떠올릴 수 있겠다.

 

보통 view model의 경우 여러 정의가 있고 여러 형식이 있지만 사실 형식이 그렇게 중요한 것은 아니기에 리액트의 커스텀 훅이라는 형식을 따르는 것을 권장한다. 커스텀 훅이라는 방식보다 더 좋거나 부족함을 느낀 케이스라면 모를까 일반적인 상황에서는 사실 커스텀 훅이라는 방식을 따르는 것 보다 적절한 상황은 없을거라 생각 하는데

 

보통 개인적으로 커스텀 훅을 통한 view model과 그 커스텀 훅과 컴포넌트를 결합한 provider 정도 레이어를 잘 사용하는 편이다. 그리고 당연히  API나 store등을 component(view) 영역에서 직접 호출하는 것을 안좋아한다. view model(또는 그 파생)이 아닌 영역을 컴포넌트 영역에서 직접 호출하는 것은 추상화나 의존성 관리 측면에서 별로 유쾌하지 않기 때문이다. 특정 컴포넌트(features)를 이해하거나 개발하기 위해서 API나 store등 중구난방의 관심사를 모두 뒤적거리는 방식이 개인적인 입장에서는 스파게티로 느껴지고 테스트 코드를 작성한다거나 리팩토링을 진행할때 또는 변경이나 확장이 있을시 마다 직관성이 많이 떨어지는게 너무 자연스럽게 느껴지는 점이라 이부분을 어떻게 설명해야 할지 잘모르겠다. 동의 하지 않는다 할지라도 한번쯤 고민해보는걸 추천해본다.

 

view model에서는 말 그대로 기존 상태와 API 등 데이터 처리에 관한 로직들을 잘쪼개서 커스텀 훅으로 관리하는데

보통 도메인 별로 쪼개서 관리하는 것을 선호한다. 그렇게 쪼개어 관리하는 훅의 경우 심할 경우에는 store의 상태에 접근하는 법을 새로 wrap한 정도가 되기도 하는데 그렇다 할지라도 쪼개어서 관리하는걸 권장한다. 애초에 해당 부분은 유지보수 측면을 고려해서 레이어를 쪼개는 것이기 때문에 동일한 추상화 레벨을 유지해야한다.


굳이 따지자면 view 영역에서 viewModel이 아닌 model (store나 api)를 직접 콜하는 것을 막는게 주요 목적이다. 추후 API콜을 한다거나 util함수를 사용한다거나 권한 체크를 위해서 state를 참고한다거나 하는 모든 부분 또는 요즘 자주 쓰이는 react-query등의 훅들 역시 해당 영역에서 wrap해서 제공하는게 좋은데. 사실 본질적으로 해당 부분이 view Model이라는 측면에서 달라지는 것은 없기 때문이다. (react-query의 경우 벡엔드 서버 그러니까 api가 모델이 되겠다.)

 

위의 원리들을 이해하고 응용한다면 사실 상태관리라는 측면에서 특별한 이슈가 있지 않는 한 크게 어려울 것은 없을 것이라 생각한다.

반응형