프론트엔드에서 아키텍처 바라보기
- -
프론트엔드에서 아키텍처를 어떻게 바라보고 또한 어떻게 발전해 왔는가를 알아보겠습니다.
여러글들을 참고하여 작성하였고 계속해서 발전시켜 나가겠습니다.
일단 프론트엔드에서 바라보는 아키텍처를 논하기 전에 우선적으로 아키텍처 자체에 대해 한번 살펴보겠습니다.
영어 단어로 아키텍처는
"건축학"
이라는 뜻입니다.
Wiki에서는 시스템 아키텍처를
"시스템 목적을 달성하기 위해
시스템의 상호작용들의
시스템디자인에 대한 제약 및 설계이다."
라고 정의하고 있습니다.
프론트엔드 아키텍처
프론트엔드에서도 아키텍처를 신경써야 하는 이유는 프론트엔드 프로젝트는 충분히 복잡하기 때문입니다.
복잡하다는 것은 개발자가 프론트엔드 프로그램을 봤을 때 인지적인 한계에 부딪히게 된다는 사실을 의미하고,
이 사실은 개발을 진행할때 뿐만 아니라 유지보수시에 들어가는 비용이 증가하는 것을 뜻합니다.
위와 같은 문제를 해결하기 위해서는 잘 설계된 프로그램을 만들어야 하는데 잘 설계 되었다는 건 '한 번에 한 부분을 제대로 집중할 수 있게 프로그램을 구성하는 것' 그리고 '간단하고 이해하기 쉽게 구성하는 것'을 의미 합니다. 즉 관심사를 잘 분리하고 코드를 잘 이해할 수 있게 만들어야 합니다. 이렇게 프로그램을 설계하는데 '아키텍처'라는 방법을 사용합니다. 그렇기 때문에 아키텍처는 프로그램을 잘 설계해서 만들고 유지하는데 투입하는 비용을 최소화 하는 목표를 갖습니다.
좋은 아키텍처??
또한, 클린 아키텍처에서는 아래와 같이 좋은 아이텍처를 정의하고 있습니다.
좋은 아키텍처는 세부사항을 정책으로부터 신중하게 가려내고, 둘이 결합되지 않도록 엄격하게 분리한다.
좋은 아키텍처는 의존성의 방향이 컴포넌트 수준을 기반으로 연결되도록 만들어야 한다.
좋은 아키텍트는 결정되지 않은 사항의 수를 최대화한다(향후 시스템에 변경이 필요할 때 어떤 방향으로든 쉽게 변경할 수 있도록 한다.)
여러가지 인식들을 접하는 것이 중요하다고 생각합니다.
프론트엔드 아키텍처의 히스토리
MVC 아키텍처
Model + View + Controller
Model: 화면에 보여줄 데이터를 담당하는 영역. Model의 정의는 주어진 환경에 따라서 다를수 있습니다. (Javascript의 Object, 서버의 DB, 서버에서 API로 요청한 데이터 등)
View: 실제 사용자에게 보여지는 화면 (HTML + CSS 로 만들어지는 결과물)
Controller: Model의 데이터를 받아서 화면에 그리고, 화면으로부터 사용자의 동작을 받아서 Model을 변경, Model과 View사이의 중간 역할을 하는 것
이렇게 MVC를 나눈 이유는
1. 화면을 다루는 문제와 데이터를 다루는 문제의 성격이 달라서 분리
2. Model과 View 간의 의존 관계를 최소화 해서 화면의 수정이 데이터 수정에 영향을 미치지 않고 데이터 수정이 화면의 수정에 영향을 미치지 않으려고
시대에 따라 MVC의 개념은 조금씩 바껴왔는데 웹서비스 초창기에는
데이터베이스를 Model
HTML, CSS, Javascript까지 포함한 클라이언트 영역을 View
가운데서 라우터를 통해 데이터를 처리하고 새로운 HTML을 만들어서 보여주는 백엔드 영역을 Controller
jQuery 시절의 MVC
ajax로 부터 받는 데이터를 Model
HTML과 CSS로 만들어지는 화면을 View
Javascript가 중간에서 서버의 데이터를 받아서 화면을 바꾸고 이벤트를 처리해서 서버에 데이터를 전달하는 Controller
당시의 가장 중요한 패러다임은 관점의 분리로 Model과 View의 종속성을 최대한 분리하는 원칙으로 HTML과 jQuery를 따로 관리하는 것이었습니다.
MVVM 아키텍처
jQuery로 작업을 하다보니 불편한 점을 발견하게 되는데 데이터를 수정하고 이벤트를 연결하고 수정하는 과정이 계속 반복된다는 것입니다. 서버에서 개발을 할 때에는 HTML이 전체적으로 렌더링되다보니 {{ }}, <%= %> 와 같은 치환자의 사용이 가능한 반면 jQuery는 수정해야 할 부분을 일일히 찾아서 수정을 해줘야 했습니다.
이러한 불편함을 해소하기 위해 Angular가 등장합니다. 앵귤러에서는 템플릿과 바인딩이라하는 중요한 개념들이 등장하였고 이후로 웹 개발하는 방식의 패러다임이 바뀌게 됩니다.
Model이 변하면 View를 수정하고 View에서 이벤트를 받아서 Model을 변경하다는 Controller의 역할은 그대로인데 이를 구현하는 방식이 jQuery와 같은 DOM 조작에서 템플릿과 바인딩을 통한 선언적인 방법으로 변하게 됩니다.
코드에서 DOM을 조작하는 코드가 사라지고 이 기능들은 프레임워크가 담당하게 됩니다. 이제 개발자는 화면에 그려져야할 데이터만 만들어서 프레임워크에 전달해주면 프레임워크가 알아서 그려줍니다.
이를 View를 그리는 Model만 다루게 되었다는 의미로 ViewModel이라고 부르며 이 방식을 MVVM이라고 부르게 됩니다.
이후 등장한 프레임워크인 React, Vue, Angular2, Svelte등 어떤 방식의 템플릿과 바인딩 문법을 쓰느냐 방식만 다를뿐 MVVM 아키텍처는 그대로 유지됩니다.
MVC에 MVVM으로 오면서 달라진 부분
- 컨트롤러의 반복적인 기능이 선언적인 방식으로 개선
- Model과 View의 관점을 분리하려 하지 않고 하나의 템플릿으로 관리하려는 방식을 발전(기존에는 class나 id등으로 간접적으로 HTML에 접근하려고 했다면 이제는 직접적으로 HTML에 접근하는 방법으로 확장)
Component 그리고 Container-Presenter 패턴
MVVM은 웹의 DOM API를 알지 못하더라고 비즈니스 로직에만 집중하면 서비스를 만들어 줄 수 있게 해주었습니다.
하나의 Page 단위가 아니라 Page안에 여러가지 모듈이 있고 Model이나 여러 화면들이 하나의 화면에서 구성 될 수 있도록 발전하게 됩니다. 그래서 MVVM이 화면단위가 아니라 조금 더 작게 재사용 할 수 있는 단위로 만들어서 조립하는 방식으로 발전하게 됩니다.
이 방식이 Component 패턴입니다. 하지만 컴포넌트 패턴에 비즈니스 로직이 들어가게 되면 컴포넌트의 재사용성은 떨어지게 됩니다.
그래서 비즈니스 로직을 포함하고 있는 컴포넌트를 container 컴포넌트, 데이터만 뿌려주는 형태의 컴포넌트를 Presenter 컴포넌트로 분리하여 최상단 혹은 1depth에 Container를 두고 비즈니스 로직을 관리하는 Container-Presenter 아키텍처가 만들어집니다.
이렇게 만들게 되었을때 컴포넌트 구조가 복잡해짐에 따라 하위 컴포넌트에 특정 값을 전달하기 위해서 중간레벨에 있는 컴포넌트들이 전부 그 props를 가지고 있어야 하는 Props Drilling 문제가 발생하게 됩니다.
FLUX 패턴과 Redux
FLUX 패턴은 MVC 패턴에서 벗어나 단방향 아키텍처(uni-directional architecture)를 만들자는 아이디어에서 시작합니다.
MVC 패턴에서 애플리케이션의 규모가 커져서 위와 같은 구조를 가지게 되었을때 View가 다양한 상호작용을 위해 여러 개의 Model을 동시에 업데이트 하고 Model 역시 여러개의 View에 데이터를 전달하는 상황이 발생합니다. 한 Model이 업데이트 되면 그에 따라 View가 업데이트되고, 업데이트된 View가 또 다른 Model을 업데이트 하는 식의 복잡한 데이터 흐름을 가지게 됩니다. 이렇게 많은 의존성을 가지게 되면 Model의 개수가 많아질수록 각 Model에서 발생한 이벤트가 애플리케이션 전체로 퍼져나갈때 이를 예측하기 힘들어 집니다.
그래서 페이스북에서 이를 해결하기 위해 Flux 패턴을 제안합니다.
Flux는 사용자 입력을 기반으로 Action을 만들고 Action을 Dispatcher에 전달하여 Store(Model)의 데이터를 변경한 뒤 View에 반영하는 단방향 흐름으로 애플리케이션을 만드는 아키텍처입니다.
Redux
이후에 Flux 패턴을 이용한 구현체로 Redux가 탄생합니다. 기존의 Props Drilling Problem의 문제점을 해결하고 Store, Dispatch, Reducer에 대한 개념을 정확하게 다시 정리해주었습니다.
Flux 패턴은 View를 각각의 MVC 컴포넌트 관점으로 보는 것이 아니라 하나의 큰 View로 이해하고 View에서는 Dispatch를 통해서 Action을 전달하면 Action은 Reducer를 통해서 Data 가 Store에 보관되고 Store에 들어있는 데이터는 다시 View로 연결되는 방식을 지향합니다.
기존의 컴포넌트 단위의 MVC 개념에서 완전히 비즈니스로직과 view를 분리하면서 이 분리된 개념을 상태관리(State Management)라고 부르게 됩니다.
Flux 패턴의 한계로는 높은 학습곡선과 장황한 문법이 지적되었습니다. 간단한 구조에서는 Props Drilling 문제가 치명적이지 않았고 상태 관리를 위해 Action, Dispatch, Reducer를 만들고 관리하는데 부수적인 코드가 많아지면서 관리가 어려워진다는 문제가 있었습니다.
Observer-Observable 패턴
Props Drilling Problem 문제를 Flux와는 다른 시각으로 해결해보고자 하는 Observer-Observable 패턴이 등장합니다.
Flux에서 Dispatch와 Action을 배제하고 값이 바뀌면 바뀐 값을 모두에게 전달한다는 개념입니다.
초창기 Mobx 가 이러한 방식을 기반으로 작성되었다고 합니다.
Angular에서는 RxJS를 받아들이고 Flux 패턴까지 결합하여 Observable과 Flux가 혼합된 상태관리를 만들기도 하였습니다.
MVI 패턴
웹프론트에서느 Cycle.js 라이브러리에서 먼저 소개된 개념입니다.
기존 Flux 패턴에서 Dispatch와 Action과 Update의 인터페이스를 전부 Observable를 이용한 Stream의 하나의 방식으로 만들어 비동기 문제를 해결하고 장황한 문법을 하나의 인터페이스로 만든점이 인상적이라고 합니다.
Model: 모델은 상태를 나타냅니다. MVI의 모델은 아키텍처의 다른 레이어와의 단방향 데이터 흐름을 보장하기 위해 변경이 불가능해야 합니다.
View: View를 나타내며 하나 이상의 Activity나 Fragment로 구현됩니다.
Intent: 사용자 또는 앱내 발생하는 Action을 나타냅니다. 모든 Action에 대해 View는 intent를 수신합니다.Presenter는 Intent를 관찰하고 Model은 새로운 상태로 변환합니다.
최근 프론트엔드 아키텍처 방향성
프론트엔드가 계속해서 발전하면서 여러가지 도전적인 아이디어들이 나오고 있는데 이에 대해 살펴보겠습니다.
Context 와 Hook
Props Drilling Problem 이 문제라면 새로운 개념을 만들기보다 Props만 Drilling되지 않는 방법을 생각해보자 하는 시각입니다. 컴포넌트 트리에서 Context라는 거대한 공통 조상을 만들고 그 Context로 부터 데이터를 제공받는 방식입니다.
개념적으로는 별도의 Store를 두는 Flux와 비슷한 개념이라 복잡한 문법을 사용하는 Redux를 React가 제공하는 Context API를 사용하겠다는 움직임이 생기고 있다고 합니다.
Svelte에서도 React와 동일한 Context라는 개념을 제공하고 별도의 Props를 선언하지 않고 모든 Props를 자식으로 전달해주는 기능들도 제공하고 있다고 합니다.
Atomic 패턴
View와 Store의 분리는 그대로 가져가되 Action, Dispatch, Store 와 같은 복잡한 구조를 해결해보기 위한 시각으로 만들어진 패턴입니다. (Recoil, Svelte Store, Vue Composition, Jotai)
간단한 문법으로 컴포넌트 외부에서 공통의 데이터를 set, get 을 할 수 있게 하면서 동시에 동기화를 할 수 있는 방향성입니다.
이와 더불어 computed, derived, select와 같은 반응형 기능을 제공하여 관련된 데이터의 동시 업데이트를 제공하고 있습니다.
View와 Model은 분리한다.
중간의 과정은 자율에 맡기고 간단하게 Model에 접근하는 법만 제공하자.
동기화, 동시성 처리가 중요
MVC의 확대 (React-Query)
프론트엔드 개발은 서버데이터를 CRUD 하고 시각으로 그리는 것에 중점되어 있는데 Flux 나 Atomic은 너무 복잡한 방법이라는 시각에서 React-Qurey에서는 고전적인 ajax 데이터를 Model로 간주 합니다.
React Query
- 서버와의 fetch 영역을 Model로 간주
- View는 React
- Controller는 query와 mutation이라는 2가지의 인터페이스를 통해서 서버의 데이터의 상태를 관리하고 캐싱, 동기화, refetch 등을 관리해주는 역할
결론
여러가지 아키텍처들과 발전에 대해 살펴보았습니다. 여러가지 아케텍처들의 큰 공통점은 View와 Business Logic 분리인것 같습니다.
이전에는 좋은 아키텍처는 무엇이고 좋은 아키텍처에 프로젝트를 맞추는 것이 아키텍처를 사용하는 방법이라는 생각을 가지고 있었는데.
여러가지 아키텍처들을 보고 발전하는 방향을 보았을때 특정 아키텍처를 정하고 거기에 프로젝트를 맞추는 것이 아니라 프로젝트 상황에 따라 아키텍처는 지속적으로 발전해야 좋은 아키텍처 설계가 아닐까 하는 생각을 가져봅니다.
Reference
'Programming' 카테고리의 다른 글
프론트엔드 개발자도 알면 좋은 Docker (2) | 2023.07.25 |
---|---|
Zod를 사용한 유효성 검증 (0) | 2023.06.19 |
React - ref, forwardRef 사용해 값 전달하기 (0) | 2023.02.07 |
이것만 알고가자 피그마 (feat. 개발자) (0) | 2022.10.03 |
이름 정의 규칙 (Naming Convention) (0) | 2022.05.31 |
소중한 공감 감사합니다