프론트엔드에서 비즈니스 로직과 뷰 로직 분리하기 (feat. MVI 아키텍쳐)

teo·2022년 8월 12일
181

테오의 프론트엔드

목록 보기
32/33
post-thumbnail

... api 요청을 다루는 핸들러를 어디에 위치 시키는 것이 좋을지 구조적 고민이 들었습니다. 핸들러를 모두 최상위 부모에 선언하고 자식은 전달만 받아 사용하는 방식으로 비즈니스 컴포넌트와 뷰 컴포넌트로 최대한 분리하려고 시도했습니다. 다만 그럴수록 최상위 컴포넌트에 핸들러가 쌓이며 지저분해지는 문제가 발생했는데, 더 좋은 방법이 있는지 궁금합니다.

프롤로그

오늘 해볼 이야기는 상태 관리, 비즈니스와 뷰 로직의 분리 프론트엔드 개발의 구조 등 프론트엔드의 아키텍쳐에 대한 이야기입니다.

프론트엔드를 하다보면 많이들 물어보는 (저 역시 지금도 고민을 하고 있는) 질문들을 꼽아보자면 "컴포넌트 추상화 구조는 어떻게 하면 좋나요?", "뷰 로직과 비니지스 로직은 어떻게 분리를 해서 정리하나요?", "어떤 구조가 좋은 구조인가요?" 등이 있습니다. 질문들의 형태는 다르지만 결국 좋은 아키텍쳐가 무엇인가에 대한 질문이라고 생각을 합니다.

처음에는 그냥 기능 구현을 하면 되지만 프로젝트의 크기가 커지다 보면 이거 제대로 정리해두지 않고서는 정말 안될 것 같은 순간들을 맞이하게 됩니다. 점점 디버깅이 힘들어지고 그냥 만들면 쉬울 요구사항도 기존 코드에 확장을 하는 것이 쉽지 않다는 것을 느끼게 됩니다. 많은 프론트엔드 개발자들이 이러한 문제에 부딫히면서 기존의 구조에서 조금씩 더 나은 아키텍쳐의 형태를 만들고 라이브러리나 프레임워크의 형태를 제안하게 되면서 짧은 시간안에 새로운 아키텍쳐들과 방법들이 꾸준히 탄생했습니다.

특정 벤더가 존재하지 않는 웹이라는 특성과 요구사항의 변화와 빠른 대응이 중요한 프론트엔드라는 직군의 특성이 만나면서 웹 프론트엔드는 더 나은 선택지로의 트렌드 변화가 참 빠른 곳이 되었습니다.

이번 글이 얼마만큼 빨리 낡아질지는 모르나 지금 현재 프론트엔드 아키텍쳐의 가장 최근의 트렌드에 대해서 한번 이야기 해볼까 합니다.

프론트엔드 트렌드 변천사

프론트엔드 아키텍쳐 변천사 8줄 요약

  1. HTML, CSS, JS의 탄생 - 관심의 분리와 느슨한 결합
  2. jQuery까지의 시대 - 일단 DOM을 쉽게 쓰자.
  3. HTML + JS를 합치는게 더 낫던데? MVC 컴포넌트 방식의 탄생
  4. 선언적으로 만들자. 데이터 바인딩 + 템플릿 = MVVM 웹 프레임워크 춘추전국시대
  5. 컴포넌트간 데이터 교환이 너무 복잡해요. Container - Presenter 방식
  6. Props Drill 문제 어떻게 할거임? - FLUX와 Redux
  7. 너무 복잡한데? - hooks와 context, Recoil, Zustand, jotai
  8. 어차피 대부분 서버 api를 관리 하려고 쓰는 거 아님? React Query, SWR, Redux Query

1. HTML, CSS, JS의 탄생 - 관심의 분리와 느슨한 결합

웹은 의도하진 않았지만 HTML과 JS와 CSS가 순서대로 탄생했고 각자의 방식대로 성장했습니다. HTML은 서버가 작성하는 영역이었고 JS는 간단한 동작과 CSS는 화면을 관리했습니다.

2. jQuery까지의 시대 - 일단 Ajax와 DOM을 쉽게 쓰자.

Ajax의 탄생으로 서버에서는 HTML을 만들지 않고 데이터만 교환이 가능하게 되면서 데이터를 JS를 이용해 DOM을 조작해야하는 작업이 중요하게 되었고 이에 따라 jQuery와 같은 Ajax와 DOM을 잘 다룰 수 있는 도구를 통해서 개발을 하게 되었습니다.

3. HTML + JS를 합치는게 더 낫던데? MVC 컴포넌트 방식의 탄생

그러다 보니 HTML과 JS를 함께 다루는 편이 더 나았고 이후 앱을 만들던 MVC의 아키텍쳐를 표방하면서 데이터를 조작하고 DOM을 조작하는 로직를 하나로 관리하려는 움직임이 생겨났습니다. 그러면서 자연스레 화면 단위가 아니라 컴포넌트 단위로 발전을 하게 됩니다. 그러면서 MVC방식으로 화면을 만들고자하는 backbonejs와 같은 프레임워크들도 만들어졌습니다.

4. 선언적으로 만들자. 데이터 바인딩 + 템플릿 = MVVM 웹 프레임워크 춘추전국시대

매번 이러한 데이터 변경 때마다 DOM을 조작해야했던 방식을 서버에서 하듯이 템플릿 방식으로 HTML을 작성하는 방안에 대해 연구되었고 이를 자동화 하는 과정에서 knockoutjsangaulrjs등을 바탕으로 본격으로 템플릿을 만들고 데이터를 바인딩하고 변경감지 전략을 통해 웹 서비스를 개발하는 MVVM 아키텍쳐가 만들어집니다. 이후 react, vue, angaulr 를 비롯한 수많은 프레임워크들이 이러한 방식을 가지고 만들어지기 시작합니다.

5. 컴포넌트간 데이터 교환과 로직이 너무 복잡해요. Container - Presenter 패턴

컴포넌트가 데이터를 많이 가지고 있고 로직이 흩어지게 되면서 어느 컴포넌트들이 복잡해지는데 재사용성은 떨어지게 됨에 따라 데이터를 받아서 보여주기만 하는 readonly스타일의 Presenter형 컴포넌트와 데이터 조작을 주로 다루는 Container형 컴포넌트를 분리하여 Container에서 props를 Presenter로 내려주면서 로직을 한 군데에 모으고 화면을 다루는 View는 재사용하는 형태의 아키텍쳐 방식이 주류가 됩니다.

6. Props Drilling 문제 어떻게 할거임? - FLUX와 Redux

이런식으로 컴포넌트를 작성하게 되면 필연적으로 상위의 props들이 하위로 전달되는 과정에서 중간에 있는 컴포넌트들을 실제 사용하지도 않고 하위에 props를 전달해주기 위해서 데이터를 유통하기 위한 props가 필요하게 됩니다. 컴포넌트의 독립과 재사용성을 위해서 컴포넌트를 분리했지만 중간에 있는 컴포넌트로 인해서 오히려 상위 컴포넌트와 하위 컴포넌트가 더 단단하게 결합을 하는 꼴이 되어버립니다. 이렇게 상위 props를 하위 컴포넌트로 전달하기 위해 하위 컴포넌트의 이벤트를 상위로 보내기 위해 중간 컴포넌트에서 사용하지도 않은 props를 추가해야하는 Props Drilling 문제가 대두되게 됩니다.

그러다보니 비즈니스 로직을 굳이 컴포넌트 계층구조로 만들 필요가 없다는 것을 알게 됩니다. 데이터를 조작하는 로직을 별개로 두고 Action이라는 매개체를 통해서 데이터를 변경을 하면 이를 컴포넌트에 직접 연결해서 사용하는 View와 비즈니스 로직을 분리하고 단방향 데이터 구조를 가지는 FLUX 패턴이 대두되고 Redux등을 통해서 사용하게 되면서 이러한 형태가 주류 아키텍쳐가 됩니다.

이때 부터 비즈니스 로직을 컴포넌트에서 분리를 하고 별도로 관리하는 도구들이 주류가 되면서 이러한 개념을 상태관리(State Management)라고 부르게 됩니다.

이후 Redux를 따라 Vue진영에서는 Vuex가 만들어지고 클래스 데코레이터를 이용하여 조금 더 쉽게 만든 Mobx도 이때 유행을 했습니다.

angaulr진영에서는 rxjs를 베이스로한 ngrx가 만들어졌고 이후 조금 더 API를 간소환 버전의 ngxs도 만들어졌습니다.

그 밖에 유한상태기계에서 영감을 받은 XState나 기타 Overmind, Effectorjs 등 여러가지 상태관리 도구들이 생겨납니다.

7. 너무 복잡한데? - hooks와 context, Recoil, Zustand, jotai

하지만 주류인 Redux의 경우 너무나도 많은 보일러플레이트가 필요했습니다. 컨셉과 시도는 좋았지만 과한 문법체계를 가지고 있었기에 이러한 아키텍쳐가 유용해짐을 느낄 수 있는 필요한 대형 프로젝트가 아니라면 중소규모에서는 대부분의 경우 오버엔지니어링이 되었습니다.

이후 React는 조금 더 간결한 문법과 이러한 외부에서 데이터를 사용할 수 있도록 hooks를 통해서 외부 비즈니스 로직을 쉽게 연동할 수 있도록 만들었고 Context를 통해서 Props Drilling 없이도 상위 props를 하위로 전달할 수 있는 방법을 제공하게 되면서 더더욱 Redux는 쓰기 싫은 기술에서 높은 순위를 차지하게 되었습니다.

그렇지만 React에서 기본적으로 제공하는 hook API만으로는 전역적인 상태관리가 용이하지 않았기 때문에 Atom이라고 불리는 전역객체를 이용해서 데이터를 기록하고 변경감지를 통해 View로 전달을 하는 형태인 Recoil이나 Zustands, jotai등의 방식등이 최근 새로운 대안으로 제시되고 있습니다.

8. 어차피 대부분 서버 api를 관리 하려고 쓰는 거 아님? React Query, SWR, Redux Query

브라우저의 경우 데이터를 로컬에 잘 보관을 하지 않기에 프론트엔드 대부분의 전역적인 상태관리가 필요한 이유는 서버와의 API에 있습니다. 왜냐하면 웹의 특성상 데이터의 보관과 조회, 수정이 서버에서 이루어져야 하고 그렇기에 비즈니스 로직이 대부분 백엔드에 보관이 되기 때문입니다.

그렇다면 View는 서버의 데이터를 보여주고 서버에 Action을 전달만 하는 경우가 대부분이다보니 전역 상태관리를 통해 비즈니스로직을 관리하기보다 다이렉트로 백엔드와 직접 연동을 하면서 필요한 로딩, 캐싱, 무효화, 업데이트 등 기존 상태관리에서 복잡하게 진행해야했던 로직들을 단순하게 만들어주는 방식도 생겨났습니다. 이러한 방식을 통해서 많은 부분을 차지하고 있었던 API를 통한 전역 상태관리가 단순하게 되는 결과를 가져왔습니다.

9. 그 밖에 소개하지 않은 눈여겨 볼만한 상태관리도구

Vue가 3.0이 되면서 Proxy를 바탕으로 하는 Composition API를 제공하였습니다. 이를 통해서 우리가 사용하던 연산자나 함수를 그대로 사용하면서도 데이터 변경감지를 할 수 있게 하여 로컬에서도 전역으로도 관리를 편하게 해주는 기능이 생겼습니다. 이러한 방식에 영감을 받아 React에서는 valtio 라는 라이브러리도 생겨났습니다.

이와 같이 기존 상태 관리 도구들의 단점인 복잡한 상용구와 Nested한 Object를 다루기 어렵다는 점을 쉽게 풀어낸 Hookstate와 같은 라이브러리도 생겨나고 있습니다.


[정리] 프론트엔드 트렌드 변화

서버에서 생성하는 결과물이 HTML에서 JSON으로 변경됨에 따라 데이터가 변경되면 화면에 반영을 하는 개발이 프론트엔드의 역할이 되었고 데이터를 화면으로 변경하는 작업을 자동화하는 방향으로 그리고 페이지 단위에서 컴포넌트 단위로 작업으로 진화하였습니다.

컴포넌트의 재사용성과 독립성이 중요해지면서 데이터를 다루는 컴포넌트와 그렇지 않은 컴포넌트를 구분하는 식으로 발전을 헀으나 컴포넌트의 계층구조와 데이터의 계층구조가 다른데 화면이 복잡해지면서 컴포넌트의 계층구조가 복잡해지다보니 컴포넌트끼리의 데이터교환까지 복잡해지는 결과를 가져옵니다.

이를 타파하기 위해 컴포넌트와 데이터를 분리하는 단방향 아키텍쳐가 제기되고 이떄 부터 컴포넌트와 데이터를 분리하는 아키텍쳐와 기조가 나타나게 됩니다. 그러면서 이러한 데이터의 변화를 다루는 상태관리가 컴포넌트 프레임워크와 분리되며 독자적인 발전양상이 나타나게 됩니다.

하지만 상태관리가 고도화됨에따라 코드가 복잡해지면서 상태관리에 대한 의구심이 더해져갑니다. 이후 컴포넌트와 비즈니스 로직은 분리하되 조금 더 간결하게 데이터의 변경감지와 전달을 할 수 있는 형태로 발전했습니다. 하나는 전역적인 데이터 스토어 방향과 서버 상태를 조금 더 편하게 다룰 수 있는 방향으로 발전하였습니다.

그밖에 상태관리를 위해 불변성관리가 필요해지면서 복잡해진 Nested한 Object를 다루는 방법들을 해결하기 위한 방안들이 모색되고 있습니다.

  1. 컴포넌트의 계층구조가 데이터의 계층구조와 다르고 더 복잡해짐 => 컴포넌트간 데이터 교환이 어려워짐
  2. 컴포넌트와 비즈니스 로직의 분리 => 상태관리의 등장
  3. 상태관리의 3가지 방향성 => 1) 고도화된 상태관리 2) 간단한 전역 스토어 3) 서버기반(API) 상태 관리

상태관리 키워드의 트렌드 추이 (FLUX, Redux를 기점으로 1번, 그리고 최근 다시 주목을 받고 있다!)

이러한 배경지식을 바탕으로 본격적인 주제인 비즈니스 로직과 뷰로직의 분리와 상태관리에 대해 이야기를 해보려고 합니다.

여담으로 웹 프론트엔드는 언제나 그렇듯 Best Practice나 이론들이 정립이 되기 전에 항상 새로운 시도들이 나오기 때문에 이론보다는 라이브러리와 커뮤니티의 아젠다들을 통해서 배우곤 합니다. 그러면서 동시에 클라이언트 개발의 트렌드를 선도하지만 체계적으로 이해하기가 어렵습니다.

그래서 웹 개발방식 cycle.js에서 영감을 받아서 ios나 android로 전파되고 정리가 된 MVI 아키텍쳐에 대한 개론을 바탕으로 그러면 앞으로 프론트엔드는 어떤식으로 구조를 가져가면 좋을지 한번 생각해보도록 하겠습니다.


(MVC → MVP → MVVM → ) 그리고 MVI 아키텍쳐

MVC, MVP, MVVM에 대한 프론트엔드 아키텍쳐의 변천사에 대한 자세한 이야기는 지난 글(프론트엔드에서 MV* 아키텍쳐란 무엇인가요?) 을 참고해주시고 이번에는 최신 아키텍쳐인 MVI(Model - View - Intent) 에 대해서 한번 알아보도록 하곘습니다.

UI 프로그래밍 개론

그전에 본격적인 아키텍쳐를 설명하기에 앞서 이해를 돕기 위해서 빌드업을 할 수 있는 UI 프로그래밍 개론을 준비했습니다.

프로그래밍에 대해서 한번 생각을 해봅시다. 최초 컴퓨터는 계산을 하기 위해 태어났고 전기신호가 켜져있다 꺼져있다의 0, 1 이진법을 통해서 숫자를 만들어내었습니다. 이렇게 표현되는 값을 데이터라고 부르게 되었습니다. 데이터는 가만히 있어서는 가치가 없습니다. 기존의 데이터에서 새로운 계산을 통해 새로운 데이터를 만들어 낼때 가치가 생겨납니다.

그래서 이러한 데이터를 보관하고 수정하고 수정하여 새로운 데이터로 계속 만들어가는 처리방법을 기술한 것이 바로 프로그램입니다. 그래고 데이터를 변화시키는 과정을 기술하는 것을 프로그래밍이라고 부릅니다.

일반적으로 사람은 0,1로만 되어 있는 이진데이터들을 알아보기가 힘듭니다. 그래서 이진수를 모아서 숫자로 만들고 문자로 만들고 배열을 만들고 객체를 만들어내면서 조금 더 이해하기 쉬운 형태의 데이터 구조를 만들어내게 되었습니다. 이러한 것들을 우리는 자료구조라고 부릅니다. 우리는 자료구조를 통해서 조금 더 이해하기 쉬운 코드와 프로그램을 작성 할 수 있게 되었습니다.

이 내용이 흥미가 있다면 이 글도 한번 읽어보세요 :)
프론트엔드 개발자에게 알고리즘 공부가 미치는 영향

User Interface

그러나 이렇게 조금 더 이해하기 쉬워진 데이터들도 일반적인 사람들은 다루기가 어려웠습니다. 필요한 데이터를 보는 것도 쉽지 않았고 데이터를 조작 하는 것은 훨씬 더 어려운 일이었습니다.

그래서 일반 사용자들에게 데이터를 다루게 하기 위해서는 조금 더 편리한 도구를 제공해야했고 이것이 데이터와 사용자 사이에 있는 가상의 매개체인 User Interface가 되었습니다.

이러한 User Interface는 기존 프로그램의 Input와 Ouput 사이에 화면과 행동을 추가하므로써 새로운 Input과 Ouput을 사용자로 부터 받도록 만드는 매개체의 역할을 하게 됩니다. (프론트엔드 개발자가 만들어 내야 하는 영역이기도 합니다.)

User Interface는 데이터와 사용자간 흐름의 방향에 따라 다음과 같이 2가지로 생각해볼 수 있습니다.

1. 데이터 → (화면) → 사용자 : 데이터의 시각화

데이터를 사용자에게 보다 친숙하고 알기 쉬운 형태로 보여줘야 하는 방법이 필요했습니다. 우리는 웹 브라우저를 통해서 이러한 기능을 제공하고 있습니다. 웹 브라우저는 HTML과 CSS를 통해서 화면을 만들어서 보여주게 되고 우리는 데이터를 적절한 형태의 HTML과 CSS로 만들기 위해서 JS를 사용하여 DOM을 조작하게 됩니다.

2. 사용자 → (행동) → 데이터: 사용자의 행동으로 데이터를 조작

이렇게 전달받은 화면을 통해서 데이터를 보면서 우리는 마우스라든가 키보드등의 외부의 입력장치를 통해서 데이터를 조작 하기 위해 특정한 행동(Behavior)(ex 버튼을 클릭)을 통해 명령을 내릴 수 있게 됩니다.

그러면 프로그램은 사용자의 발생시킨 이벤트(Event)를 통해서 사용자 행동의 의도(Intent)에 맞는 동작(Action)을 전달하게 되면 이에 맞게 적절한 데이터의 변화가 발생하게 되고 이는 다시 첫번째로 돌아가게 되면서 순환구조를 가지게 됩니다.

MVI? Model - View - Intent

사용자가 다른 동작을 해도 실제로 같은 데이터의 변화를 의미하는 경우가 있습니다. 가령 사용자가 "+" 버튼을 눌렸을떄 숫자가 1 증가 하게 하거나 위쪽 방향 키보드를 눌렀을때에도 숫자를 1 증가하게 할 수 있습니다. 이경우 사용자가 한 행동은 다르지만 데이터의 입장에서는 같은 동작을 수행한 셈이 되었습니다.

우리는 이를 통해서 비즈니스로직을 2가지 레이어로 나눌 수 있다는 것을 알게 되었습니다.

1) 사용자가 View를 통해서 전달한 UI Event를 어떠한 데이터 변화를 하게 할지 전달하는 역할
2) 전달받은 요청에 따라서 적절히 데이터를 변화하는 역할

1)을 사용자의 의도을 파악하는 것이라고 보고 Intent라 명하고
2)는 데이터를 다루므로 전통적인 이름인 Model이라고 하여 Model - View - Intent 라는 아키텍쳐가 되었습니다.

MVI의 특징

MVI 아키텍쳐가 기존의 MVC나 MVVM과는 다른 점은 이 구성이 하나의 컴포넌트가 아니라 앱 전체에 적용이 된다는 점입니다. 그래서 전체적으로 데이터의 방향성이 단방향으로 연결이 되며 데이터가 전역적으로 구성이 된다는 점이 특징입니다. 뷰는 모델에 의존적이지만 비지니스 로직은 뷰와의 의존성이 없기 때문에 화면변화에 유연하며 별도로 테스트를 하기 쉽습니다. 또한 컴포넌트간 데이터 통신에 의존하지 않기 때문에 일관성있는 상태를 유지하기에 용이합니다.

  • 데이터가 단방향으로 순환합니다. → 데이터의 흐름을 이해하고 디버깅을 하기 쉬워집니다.
  • 비즈니스로직이 뷰에 의존하지 않습니다. → UI변화 요구사항에 유연하게 대응할 수 있습니다.
  • View의 생명주기와 무관하게 일관성 있는 상태를 갖습니다. → 컴포넌트 생명주기에 따른 상태 동기화 문제를 해결합니다.

그렇습니다. 눈치 채신 분들이 있겠지만 FLUX 패턴, Redux 패턴, 상태관리등 그 동안 프론트엔드에서 줄곧 제시되었던 방법들을 잘 정리하여 아키텍쳐로써 설명을 하고 있는 모양새입니다.

프론트엔드 아키텍쳐의 방향성 요약정리

결론만 정리하자면 재사용이 가능한 독립적인 컴포넌트를 조립하여 서비스를 구성한다는 기존 구조와 달리 뷰와 비즈니스로직을 완전히 분리하여 생각합니다.

비즈니스 로직은 1) 데이터를 관장하는 Model2)사용자의 행동을 데이터 변화로 매핑하는 Intent영역으로 분리합니다.

Model은 다시 1) 변화를 감지하고 변경사항을 전파하는 영역2) 데이터를 변화하는 로직 을 구분하여 작성합니다.

이를 통해 View에서는 Model로 부터 데이터를 조회하는 Query와 데이터를 변화시키는 Command를 언제든 조립해서 사용(CQRS 패턴) 할 수 있게 되어 뷰는 비즈니스 로직에 의존적이지만 뷰끼리는 느슨하게 결합되어 UI 요구사항 변화에 긴밀하게 대응할 수 있게 됩니다.

그렇다면 기존 방법은 안 좋은 것일까?

그렇다고 전통적인 독립적인 UI 컴포넌트가 필요없다는 얘기는 아닙니다. 컴포넌트 계층의 맨 아래에 있는 재사용이 가능한 독립적인 UI 컴포넌트를 조립하여 화면을 만드는 전략은 여전히 유효합니다.

그러나 Props Drilling을 유발하며 경직된 구조를 만들어내는 중간 계층의 컴포넌트들은 사실 독립적일 필요도 없고 재사용도 되지 않는 컴포넌트인 경우가 많습니다. 이러한 컴포넌트들은 대개 비즈니스 로직를 담당하기 때문에 거대한 컴포넌트(Massive-View-Controller) 되기 마련입니다.

컴포넌트는 독립성을 유지하고 의존성을 최소화 해야한다는 원칙에 따라 최대한 props를 통해서 컴포넌트 계층 구조를 유지하던 방식이 되려 경직된 구조를 만들어냈기에 어차피 재사용하지 못할 컴포넌트라면 비즈니스 모듈에 의존적이더라도 언제든 교체가능한 방식으로 만들어내는 것이 변화에 대응하기 좋다는 것입니다.

One more thing: Intent와 Reducer

FLUX 패턴에서 생소한 개념인 Reducer는 왜 이렇게 작성을 해야하는지에 대해서 의문을 품는 사람들이 많습니다. 일반적으로는 보통 이벤트 핸들러에서 직접 모델의 여러가지 데이터들의 변화를 기술하는 식으로 작성을 하는 경우가 많습니다.

// View에서 직접 여러가지 값들의 변화를 기술한다.
const onClick = () => {
  setVisible(!visible)
  setCount(count + 1)
  setTodos([...todos, {title: "할일추가"})
}

이러한 방식은 우리가 알고 있는 실행순서와 일치하기 때문에 작성을 할 때에는 어렵지 않습니다. 그러나 문제는 디버깅을 해야할 때인데요 디버깅을 결과를 보고 원인을 찾아내는 역순의 과정을 밟아야 하기 때문에 특정 데이터의 변화를 다 추적을 해야할 필요가 생깁니다. 이때 데이터를 변화하는 뷰 코드에 흩어져있다면 어떤식으로 데이터가 변화하는지 추적이 어렵습니다.

뿐만 아니라 뷰에서 직접 데이터를 수정하도록 작성을 하게 되면 뷰와 모델간의 의존성뿐만 아니라 모델과 뷰간의 의존성이 생기게 됩니다.

따라서 뷰에서는 의도만을 전달하고 의도에 맞는 데이터 변환은 모델에서만 처리할 수 있도록 만들어 데이터를 변화하는 코드를 Model 모듈에 모으게 되면 응집도가 높아지고 추후 디버깅에 용이하며 특히 뷰가 비즈니스로직으로 부터 느슨한 결합을 할 수 있도록 만들어 줍니다.

이해를 돕기 위한 의사코드

내가 사용하고 있는 상태관리를 떠올려보세요. 최소한의 코드로 데이터의 흐름과 아키텍쳐의 느낌만 이해할 수 있도록 작성해봤습니다.

// Action
export const _INCREASE = action("_INCREASE")
export const _DECREASE = action("_DECREASE")
export const _RESET = action("_RESET")

// Model
export const store = createStore({count:0}, state => {

  on(_INCREASE, (value) => state.count += value)

  on(_DECREASE, (value) => state.count -= value)

  on(_RESET, () => state.count = 0)
})

// Component
export const App = (props) => {

  // Query
  const count = SELECT(store.count)
  
  // Computed Value
  const doubledCount = count * 2
  
  // Intent
  const onClick = () => dispatch(_INCREASE(+1))
  
  // View
  return <button onCLick={onClick}>{count}</button>
}

이러한 방식으로 작성을 하게 되면 화면을 작성을 할때 컴포넌트간의 종속성이나 비즈니스 모델의 구현과 관계없이 유연하게 화면을 구성할 수 있게 됩니다.

// Component1
const Component1 = (props) => {

  const count = SELECT(store.count)

  return <div>{count}</div>
}

// Component2
const Component2 = (props) => {

  const onClick = () => dispatch(_INCREASE(+1))
  
  return <>
	<Component1/>
    <button onCLick={onClick}>1 증가</button>
  </>
}  

못다한 이야기들

MVI 아키텍쳐에서의 props의 역할, Computed value, Side Effect, 서버 API의 연동, 비동기 로직 처리, Nested Object 다루기 등 실전으로 들어가게 되면 조금 더 알아야할 내용들이 많습니다.

해당 내용들은 MVI 아키텍쳐 고급편이라는 주제로 다시 작성을 해볼 생각입니다.

이번 글에서는 대략적인 프론트엔드 아키텍쳐의 변화의 방향와 최근 아키텍쳐가 지향하는 형태 그리도 뷰 로직과 비즈니스 로직의 분리가 어떤 것이며 최근 사용하고 있는 상태관리 라이브러리들이 지향하는 바가 무엇인지 트렌드를 느낄 수 있는 글이었기를 바랍니다.

끝으로...

프론트엔드 아키텍쳐 트렌드는 정말 빨리 바뀌고 있습니다. 몇 년만 지나면 벌써 내 코드나 아키텍쳐가 낡은 방식이 되고 맙니다. 조금 지나기는 했지만 여전히 Redux에 의구심을 품으면서 상태관리를 중심으로 여러가지 대안들이 제시되고 있는 모습입니다.

컴포넌트의 독립성과 재사용을 중심으로 컴포넌트의 계층구조를 통해서 화면을 만들어가는 과정에서 비즈니스 로직의 계층구조와 뷰 로직의 계층구조가 달라 비즈니스 로직이 파편화가 되거나 하나의 컴포넌트에 거대 집중화가 되면서 그리고 거대한 화면을 만들다 보니 컴포넌트간 데이터를 교환하는 과정에서 불필요한 props등의 통로를 만들게 되면서 현재의 방식의 의구심을 품고 새로운 구조들을 고민했습니다.

그 결과 비즈니스 로직과 뷰를 완전히 분리하여 사용자의 행동으로 데이터의 변화를 관리하고 변경감지를 전파하며 직접적으로 뷰에 전달을 하는 방식의 아키텍쳐가 나오게 되었습니다. 그리고 이는 좋은 아키텍쳐였습니다. 그러나 이러한 의도와는 달리 복잡한 코드를 양산하는 형태가 만들어지면서 다시 한번 새로운 형태를 고민하고 있습니다.

적어도 뷰와 비즈니스 모델을 분리하고 전역적인 형태로 데이터 관리가 필요하다는 데에는 동의를 한 모양새입니다. 그러면서 불 필요한 보일러플레이트를 최소화하는 방향으로 그리고 거대한 구조를 만들기 보다는 작고 간단히 사용할 수 있는 형태부터 점진적으로 복잡하게도 할 수 있는 방향으로 나아가려고 하고 있습니다. 또한 서버 상태를 새로운 상태로 간주하는 방식이 새로운 방향성의 갈래가 되어 가고 있습니다.

이러한 컨셉들은 다른 프론트세계(ios, android)로 전해져서 MVI라고 하는 이름의 아키텍쳐로 발전하고 있습니다. 아직 MVI 아키텍쳐의 완전한 형태가 정해지지는 않았지만 뷰와 비즈니스 로직을 거시적으로 분리하고 행동과 데이터를 분리하여 단방향 흐름을 만들어내고 전역적인 스토어에서 변경사항을 전파한다는 흐름까지는 동의하고 있는 것 같습니다.

패러다임과 아키텍쳐는 실제가 아니라 방향성입니다. 내가 쓰고 있는 라이브러리가 정확히 이러한 형태에 맞지 않을 수 있습니다. 그렇지만 이러한 대략적인 방향성과 흐름을 이해한다면 여러가지 상태관리 라이브러리를 선택하거나 컴포넌트의 구조를 설계할때 더 넒은 시각으로 바라볼 수 있을 거라고 생각합니다.

이 글이 좀 더 나은 컴포넌트와 프론트엔드의 구조를 설계하는데 도움이 될 수 있는 마중물이 되길 바라며 다음번에는 조금 더 깊은 이야기로 찾아뵙겠습니다.

긴 글 읽어주셔서 감사합니다. ❤️

profile
Svelte, rxjs, vite, AdorableCSS를 좋아하는 시니어 프론트엔드 개발자입니다. 궁금한 점이 있다면 아래 홈페이지 버튼을 클릭해서 언제든지 오픈채팅에 글 남겨주시면 즐겁게 답변드리고 있습니다.

24개의 댓글

comment-user-thumbnail
2022년 8월 14일

항상 좋은글 감사합니다 😊

1개의 답글
comment-user-thumbnail
2022년 8월 14일

좋은 글 감사합니다 :)

1개의 답글
comment-user-thumbnail
2022년 8월 14일

사이드 프로젝트에 한 번 도입해봐야겠네요 😃
덕분에 프론트엔드의 아키텍쳐에 대한 궁금증이 풀렸습니다!

1개의 답글
comment-user-thumbnail
2022년 8월 15일

최근 본 글 중에 가장 재밌게 읽었습니다. ㅎㅎ 처음 올려주셨을 때 읽어보고 어려운 내용이라 다시 읽어볼려고 들어왔는데 아키텍쳐 뿐만 아니라 컴포넌트 설계 패턴까지 함께 작성해주셔서 더욱 이해하기 쉬웠습니다 ㅎㅎ

1개의 답글
comment-user-thumbnail
2022년 8월 16일

오늘도 출근길에 잘 읽으면서 가요! 이따 퇴근해서 한 번 더 읽어봐야겠습니다. 항상 좋은 글 올려주셔서 감사합니다!!

좋은 하루 되세요~

1개의 답글
comment-user-thumbnail
2022년 8월 16일

비즈니스 로직과 뷰 로직 분리에 대한 글은 수없이 많이 읽어보았지만, 그 중에서도 많은 고민이 묻어나는 글이었습니다. 여러번 읽어볼 가치가 있는 글이네요. 감사합니다.

1개의 답글
comment-user-thumbnail
2022년 8월 16일

글 잘 읽었습니다! MVI 새로운 트렌드를 알게 됐습니다! ㅎㅎ

1개의 답글
comment-user-thumbnail
2022년 8월 16일

그림 뭐로 그리신거에요?ㅋㅋ 귀염뽀짝 하네요
글 잘봤습니다!

1개의 답글
comment-user-thumbnail
2022년 8월 16일

좋은 글 감사합니다~~

1개의 답글
comment-user-thumbnail
2022년 8월 16일

테오, 출근길에 재밌게 읽었어요:) 요즘 어디에서나 컴포넌트 구조화에 대해 이야기를 많이 하더라고요. 흐름은 바뀌는 거 같은데 정확히 코드로 어떻게 적용되는지, 코드를 작성했을 때 괜찮은 방식인지 잘 몰라서 다같이 고민하고 있는 게 아닐까 싶네요. 테오의 말씀대로 intent를 중심으로 생각하는 게 상당히 신선하네요. 이게 사용자 중심에서 생각할 수 있는 방식 같아요. 저는 코드를 작성할 때 사용자가 아닌 디자인이나 컴포넌트 중심으로 생각하는 편인데, 다시 봐라봐야겠어요. 설명을 정말 자세히 해주셨는데 리덕스와 같은 상태관리를 쓰지 않고도 MVI 패턴을 적용할 수 있는 방식을 코드로 어떻게 표현해야 할지 잘 느낌이 오지 않아서 공부 좀 해봐야겠다는 생각이 드네요. 오늘 회사가면 팀원들이랑 공유도 해야겠어요. 이번 글 유난히 잘 읽히네요 ㅋㅋ 정말 매번 감사해요❤️

1개의 답글
comment-user-thumbnail
2022년 8월 21일

대단한 수준의 설명이네요. 감탄하며 읽었습니다.

React hook만으로는 전역적인 상태관리가 용이하지 않았기 때문에

프론트를 잘 몰라서 이 대목에서 의아했습니다. 추가 예시를 부탁드려도 될까요?

1개의 답글