프론트엔드 개발에서 디자인 패턴은 소프트웨어 디자인에서 반복적으로 발생하는 문제를 해결하기 위한 일반적인 해결책이나 구조를 제공한다.
아키텍처와 디자인 패턴은 소프트웨어 개발에서 서로 다른 개념이다.
아키텍처(Architecture)는 소프트웨어 시스템의 전체 구조와 구성 요소 간의 상호작용을 설계하는 것을 의미한다. 아키텍처는 시스템의 큰 그림을 그리고, 시스템의 기능적인 측면과 비기능적인 측면을 고려하여 구성 요소, 모듈, 레이어 등을 결정한다. 아키텍처를 시스템의 기본적인 구조와 구성을 설계하는 데 초점을 맞춘다.
아키텍처는 건물을 디자인할 때의 전체적인 구조와 계획을 의미한다. 건물의 크기, 층수, 방의 배치 등을 결정하는 것이다. 아키텍처는 전체 시스템의 큰 그림을 그리고, 시스템의 기능과 구성 요소들 사이의 상호작용을 설계한다.
디자인 패턴(Design Pattern)은 소프트웨어 디자인에서 자주 발생하는 문제에 대한 재사용 가능한 해결책이다. 디자인 패턴은 특정 상황에서 효과적인 설계와 구현을 위해 일반적인 구조와 상호작용을 제공한다. 디자인 패턴은 코드 수준에서 구체적인 문제를 해결하는 데 중점을 둔다. 디자인 패턴은 설계 원칙을 따르고, 코드의 유연성, 재사용성, 유지보수성을 향상시키기 위해 사용된다.
디자인 패턴은 건물을 디자인할 때 발생하는 일반적인 문제들에 대한 해결책이다. 예를 들어, 같은 건물 내에서도 각 층마다 다른 유형의 방을 디자인해야 할 때, 층별로 특정한 패턴을 적용해서 방을 배치할 수 있다. 이렇게 패턴을 사용하면 비슷한 문제에 대해 일관된 방식으로 해결할 수 있다.
간단히 말하자면, 아키텍처는 전체 시스템의 큰 틀과 구성을 설계하는 것이고, 디자인 패턴은 작은 규모의 디자인 문제를 해결하기 위한 재사용 가능한 아이디어나 접근법을 제공한다. 즉, 아키텍처는 시스템의 구조를 설계하고, 디자인 패턴은 문제에 대한 해결책을 제공하는 역할을 한다.
제일 먼저 등장한 MVC
(Model-View-Controller) 패턴은 소프트웨어 개발에서 사용되는 디자인 패턴 중 하나로, 애플리케이션 구성 요소를 세 가지 역할로 분리하여 설계한다.
모델 (Model): 애플리케이션의 데이터와 비즈니스 로직을 담당한다. 데이터베이스와의 상호작용이나 데이터의 가공, 유효성 검사 등을 처리한다. 모델은 독립적으로 존대하며, 변경이 발생할 때 컨트롤러와 뷰에 알리는 역할을 한다.
뷰 (View): 사용자에게 데이터를 시각적으로 표현하는 역할을 한다. 주로 사용자 인터페이스 요소들로 구성되며, 모델의 데이터를 표시하고 사용자의 입력을 컨트롤러로 전달한다. 여러 개의 뷰가 하나의 모델을 공유할 수 있다.
컨트롤러 (Controller): 사용자의 입력을 받고, 해당 입력을 처리하여 모델을 갱신하고 적잘한 뷰를 업데이트한다. 사용자와 상호작용하고 애플리케이션의 흐름을 제어하는 역할을 한다. 컨트롤러는 모델과 뷰 사이의 중개자 역할을 수행하며, 모델과 뷰의 의존성을 제거한다.
MVC
패턴의 주요 목표는 애플리케이션의 구성 요소를 분리하여 개발과 유지보수를 용이하게 만드는 것이다. 모델, 뷰, 컨트롤러의 역할을 명확히 구분함으로써 코드의 재사용성, 확장성, 유지보수성이 향상된다. 또한, 애플리케이션의 로직과 사용자 인터페이스를 분리하여, 개발자들 간의 협업을 용이하게 하며, 코드의 가독성과 테스트 용이성을 높일 수 있다.
MVC
패턴은 jQuery
의 전 후로 개념이 조금 다르다.
- 데이터베이스를 Model로 취급한다.
- HTML과 CSS, 그리고 JS까지 포함한 클라이언트 영역을 View로 취급한다.
- 가운데에서 라우터를 통해 데이터를 처리하고 새로운 HTML을 만들어서 보여주는 백엔드 영역을 Controller라고 취급한다.
이후 jQuery
가 나오고 프론트엔드의 역할이 나오면서 ajax
라는 기술이 나오고 HTML을 서버에서 직접 만들 필요가 없게 된다.
- ajax로 부터 받는 데이터를 Model로 취급한다.
- HTML과 CSS로 만들어지는 화면을 View로 취급한다.
- JS가 중간에서 서버의 데이터를 받아서 화면을 바꾸고 이벤트를 처리해서 서버에 데이터를 전달하는 Controller의 역할을 수행하게 된다.
jQuery
를 사용할 때 불편한 점은 바로 데이터에 접근하기 귀찮다는 점이다.
매번 데이터 변경 때마다 템플릿 방식으로 HTML을 작성하는 방안이 연구되었고, 이를 자동화 하는 과정에서 Angaulr.js 등을 바탕으로 웹 서비스를 개발하는 MVVM
패턴이 만들어진다.
MVVM(Model-View-ViewModel) 패턴은 MVC 패턴의 확장된 형태다. MVVM
은 사용자 인터페이스와 비즈니스 로직 사이의 관계를 더욱 강력하게 연결하여 개발자들에게 유연성과 생산성을 제공한다.
MVVM
패턴은 다음과 같은 구성 요소로 이루어져 있다.
모델 (Model): 애플리케이션의 데이터와 비즈니스 로직을 담당한다. MVC 패턴과 동일한 역할을 수행한다.
뷰 (View): 사용자에게 데이터를 시각적으로 표현하는 역할을 한다. 사용자 인터페이스 요소들로 구성되며, 사용자 입력에 반응하여 뷰 모델로부터 데이터를 가져와 표시한다.
뷰 모델(View Model): 뷰와 모델 사이의 매개체 역할을 한다. 뷰 모델은 뷰에 표시할 데이터를 제공하고, 뷰에서 발생하는 이벤트와 사용자 입력을 처리한다. 뷰 모델은 뷰에 대한 상태를 유지하며, 모델로부터 데이터를 가져와 뷰에 전달하거나, 뷰에서 발생한 이벤트를 모델에 전달한다.
MVVM 패턴의 핵심 아이디어는 데이터 바인딩이다. 뷰와 모델 사이에 양방향 데이터 바인딩을 통해 뷰의 상태와 뷰 모델의 상태를 자동으로 동기화시킨다. 이를 통해 개발자는 수동적인 데이터 업데이트나 이벤트 핸들링을 할 필요 없이 뷰 모델에게 작업을 위임할 수 있다. 이러한 데이터 바인딩은 개발자의 생산성을 향상시키고, 애플리케이션의 유지보수성을 향상시킨다.
MVVM
패턴은 주로 웹 및 앱 개발에서 사용되며, 프론트엔드 프레임워크나 라이브러리인 Angular, Vue.js, React 등에서 MVVM
패턴을 지원하고 있다. 이러한 프레임워크를 사용하면 MVVM
패턴의 이점을 더욱 쉽게 활용할 수 있다.
간단히 말하자면, Model이 변하면 View를 수정하고 View에서 이벤트를 받아서 Model을 변경하는 패턴으로 동작한다. 즉, View에서 Controller의 역할을 수행하여 Model을 변경하는 것이다.
기존 MVVM
패턴은 엄청난 파장을 일으켰고, 하나의 Page 단위가 아닌 Page 안에 여러가지 모듈이 있고 그 모듈을 조립하여 하나의 화면에서 구성하도록 하는 패턴으로 발전한다.
이것이 바로 Component
패턴이다.
컴포넌트의 재사용이 가능해야 한다는 원칙이 있어 비즈니스 로직을 포함시키지 않으려 했다. 왜냐하면 비즈니스 로직이 들어가면 재사용성이 좋지 않기 때문이다. 그래서 비즈니스 로직이 필요한 컨포넌트를 Container
컴포넌트라고 명칭하고 비즈니스 로직이 없는 데이터만 뿌려주는 컴포넌트들을 Presenter
컴포넌트로 분리하여 관리하게 된다.
Presenter형 컴포넌트: 데이터를 받아서 보여주기만 하는 readonly
스타일 컴포넌트
Container형 컴포넌트: 데이터 조작을 주로 다루는 컴포넌트
Component
패턴은 UI 컴포넌트의 재사용성과 독립성을 강조하며, Container-Presenter
패턴은 비즈니스 로직과 UI 로직의 분리를 중시한다. 두 패턴 모두 코드의 구조화와 유지보수를 용이하게 만들어주는 장점을 가지고 있다.
이후
Container
에서props
를Presenter
로 내려주면서 로직을 한 군데에 모으고 화면을 다루는 View 방식이 재사용 형태의 아키텍처 주류가 된다.
Props Drilling Problem은 React 애플리케이션에서 발생할 수 있는 문제다. 이 문제는 컴포넌트 계층 구조에서 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달할 때, 중간에 위치한 여러 컴포넌트를 거쳐야 하는 번거로움과 복잡성을 의미한다.
예를 들어, 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달해야 할 경우, 중간에 위치한 여러 컴포넌트를 거쳐서 props
를 전달해야 한다. 이렇게 되면 컴포넌트 계층이 깊어질수록 props
를 전달하는 과정이 복잡해지고 유지보수가 어려워진다. 또한, 중간에 위치한 컴포넌트들은 전달받은 props
를 사용하지 않지만 props
를 전달하기 위해 필요한 코드를 작성해야 하므로 코드의 가독성이 저하될 수 있다.
이렇게 상단에 있는 데이터를 하단에 있는 props
로 보내기 위해서 중간 계층에 있는 props
를 하나씩 추가하는 문제를 Props Drilling Problem이라고 부른다.
FLUX
패턴은 이제 MVC
의 개념에서 벗어나서 단방향 아키텍처를 만들자는 이야기로 시작한다.
컴포넌트의 재사용과 독립성을 지나치게 강조하다보니 같은 데이터를 공유하는 과정에서 props
를 통해서 데이터를 전달하는 문제들로 하여금 Model의 관리가 파편화 되는 문제가 발생했다.
기존의 컴포넌트를 지향하는 MVC
가 아니라 View를 하나의 범주로 두고 View에서 Action을 호출하면 Dispatcher를 통해 Store라는 공간에 Data가 보관이 되고 다시 뷰로 전달되는 흐름을 설명한다. FLUX
는 아키텍처만 공개되었을 뿐 실체는 없었기에 바로 쓰이지는 않았고 아젠다를 만들어 주는 정도에서 그쳤다.
이후 FLUX
패턴을 이용한 구현체인 Redux
가 탄생한다. 그리고 이 Redux
는 센세이션을 일으킨다. 기존의 Props Drilling Problem의 문제점을 정확하게 짚어주었고,
Store, Dispatch, Reducer에 대한 개념을 정확하게 다시 정리해주었다.
FLUX
패턴은 View를 각각의 MVC
컴포넌트 관점으로 보는 것이 아니라 하나의 큰 View로 이해하고 View에서는 Dispatch를 통해서 Action을 전달하면 Action은 Reducer를 통해서 Data가 Store에 보관이 되고 Store에 들어있는 데이터는 다시 View로 연결이 되는 방식을 지향한다.
기존의 컴포넌트 단위의
MVC
개념에서 완전히 비즈니스 로직과 View를 분리하면서 이 분리된 개념을 상태관리(State Management)라고 부르게 된다.
주류가 된 Redux
는 너무 많은 보일러플레이트(최소한의 변경으로 여러 곳에서 재사용되며, 반복적으로 비슷한 형태를 띄는 코드)가 필요했다. 컨셉과 시도는 좋았지만, 과한 문법 체계를 가지고 있었기 때문에 대형 프로젝트가 아니라면 중소규모에서 대부분은 오버엔지니어링이 되었다.
이후 React는 조금 더 간결한 문법과 외부에서 데이터를 사용할 수 있도록 Hooks를 통해서 외부 비즈니스 로직을 쉽게 연동할 수 있도록 만들었다. 또한 Context를 통해서 Props Drilling 없이도 상위 props
를 하위로 전달하는 방법을 제공하면서 Redux
는 더더욱 쓰기 싫은 기술이 되었다.
그렇지만 React에서 기본적으로 제공하는 hook API만으로는 전역적인 상태관리가 용이하지 않았다. 그래서 Atom
이라고 불리는 전역객체를 이용해 데이터를 기록하고, 변경감지를 통해 View로 전달하는 형태인 Recoil
이나 Zustand
, Jotai
같은 방식이 새로운 대안으로 제시되고 있다.
그렇게 아토믹 패턴이라는 패턴이 새롭게 등장하였다.
Recoil, Svelte Store, Vue Composition 등이 있으며 이는 Redux
와 같이 Action - Dispatch - Reducer 처럼 굳이 복잡하게 만들어야 하냐에 대한 의문이 들어서 만들어진 패턴이다.
Redux
보다 간단한 문법으로 컴포넌트 외부에서 공통의 데이터를 set, get을 할 수 있게 해준다.
어차피 대부분 서버 API를 관리하려고 쓰는 거 아닌가?
대부분의 프론트엔드에서 전역적인 상태관리가 필요한 이유는 서버와 API에 있다. 웹의 특성상 데이터의 보관과 조회, 수정이 서버에서 이루어져야 하고, 그렇기에 비즈니스 로직이 대부분 백엔드에 보관되기 때문이다.
그렇다면 View는 서버 데이터를 보여주고 서버에 Action을 전달만 하는 경우가 대부분이었다. 그러자 백엔드와 직접 연동해 기존 상태관리에서 로딩, 캐싱, 무효화, 업데이터 등 복잡하게 진행했던 로직들을 단순하게 만들어주는 방식도 생겨났다.
이러한 방식으로 통해 많은 부분을 차지하고 있었던 "API를 통한 전역 상태관리"가 단순해지는 결과가 나왔다.
React-Query는
- 서버와의 Fetch 영역을 Model로 간주
- View는 React
- Controller는 query와 mutation이라는 2가지 인터페이스를 통해서 서버 데이터의 상태를 관리하고 캐싱, 동기화, refetch등을 관리해주는 역할
사실 프론트엔드 개발자가 디자인 패턴을 왜 배워야하지? 라는 생각이 있었는데, 이번에 여러가지로 찾아보면서 웹 발전에 역사에 대해서 더 깊게 알 수 있어서 재밌었다.
특히 React-Query나 Recoil, Zustand는 그냥 "전역 상태관리해야 하니까"라는 생각으로 사용만 했었지 왜 만들어졌고 지금은 어떻게 평가되고 있는지 전혀 몰랐었는데, 이번 기회에 제대로 알게 되었다고 생각한다.
참고 문서들 중 테오의 프론트엔드 벨로그가 있는데 정말 좋다. 프론트엔드 개발자라면 한 번씩은 정독한다면 많은 도움이 될 것이라고 생각한다.
참고
프론트엔드 아키텍처의 가장 최근 트렌드는?
프론트엔드의 아키텍처? 디자인 패턴?
프론트엔드에서 MV* 아키텍쳐란 무엇인가요?