아키텍처프론트엔드

[ Programming > 아키텍처 ]

[아키텍처] 아키텍처

 Carrot Yoon
 2025-10-21
 6

아키텍처

프론트엔드는 MVC, MVP, MVVM, FSD, BFF 등 다양한 아키텍처가 사용되요. 그래서 오늘은 이러한 아키텍처들에 대해서 공부해봐요.

MVC 아키텍처

MVC 아키텍처는 약 36년 전에 만들어진 아키텍처로 Model - View - Controller의 3가지 Layer로 이루어진 아키텍처에요.

  • Model : 데이터와 비즈니스 로직을 관리하는 부분이에요.

  • View : UI를 통해 데이터를 화면으로 보여주는 부분이에요. UI 로직도 함께 포함되요.

  • Controller : Model과 View 사이의 다리 역할을 해요. user input을 처리하고, model을 업데이트하고 그 결과를 view에게 전달해요.

전통적 MVC

image.webp

유저가 버튼 같은 것을 클릭하면 Event가 전달되고, Controller는 이에 따라 Model 업데이트를 요청할 수도 있고 View 업데이트를 요청할 수도 있어요. 그리고 Model은 업데이트 되면 View에게 최신 Model을 반영한 View를 UI로 제공하도록 만들 수 있어요.

이 때 Model의 변경으로 통한 View 업데이트는 크게 2가지 방법을 사용해요. Observer 패턴을 이용하여 자동 반영되게 만들거나, 단순히 View를 업데이트하는 코드를 넘겨줘 Flow를 통해 View 업데이트를 유발할 수 있어요.

Web MVC (feat. SPA)

image.webp

MVC모델을 Web에 적용했을 때 모습이에요. Redux의 데이터가 View에 나타나고, View의 Event를 통해 Controoler의 Hook이 작동하여 Model을 변경하는 Action을 유발해요.

물론 위 구조는 억지로 짜맞춘 구조라고 보기도 해요. 왜냐하면 요즘 최신 프론트엔드 앱들은 마치 바로 Model 변경된 View에 반영되어 보여주는 것처럼 동작하기 때문이에요. 실제로 그 내부적으로는 Action을 일으키는 함수들이 작동하지만요.

Full-Stack MVC

image.webp

MVC모델을 Fullstack 관점에서 봤을 때 모습이에요. Web App에서는 UI 로직만 담당하고, 비즈니스 로직들은 API를 통해 사용되요.

Hierachical MVC

image.webp

Hierachical MVC는 여러 MVC로 이루어지는 아키텍처를 의미해요. 예를 들면 React의 Page는 여러 Component들로 구성되어 있어요. 네이버를 예로 들면, 뉴스 기사 보여주는 위젯, 날씨 보여주는 위젯, 광고 보여주는 위젯 등 여러 위젯으로 이루어져 있고 각각 MVC 패턴으로 위젯이 구성되어 있음을 알 수 있어요.즉 컴포넌트 별로 MVC 아키텍처로 별개의 도메인으로 관리가 되고있음을 파악할 수 있어요. 이게 Hierachical MVC에요. 모듈로 분리하여 유지보수성도 좋아지고 재사용성도 좋아질 것 같아요.

MVC와 Thin, Thick(fat) Client

image.webp

MVC에서 Web Client의 책임의 정도에 따라 Thin Client, Thick Client로 나뉘어져요.

  • Thin Client : Frontend는 UI 로직과, 인터렉션만 담당하고, 다른 모든 로직은 Backend에서 담당해요. 전통적인 MPA에서 사용되는 방법이에요. 버튼 누를때 서버에 통신이 가고 reload가 되겠죠??

  • Thick Client : Frontend에서 더 많은 책임을 담당해서 서버의 부담을 덜어줘요. 간단히 말하면 클라이언트가 서버에 의존성이 줄어들고 독립적으로 실행할 수 있는 기능들이 많아져요. 예를 들면 버튼이 누를때마다 서버를 통해 reload가 되지도 않고 일부만 알아서 부분적으로 렌더링을 한다던가 할 수 있어요. 상태 관리, 로직 관리, 심지어 백엔드 데이터를 저장하기도 해요.

정리하자면 MPA의 Thin Client는 클라이언트 단에서 훨씬 가볍게 실행되서 안좋은 디바이스에서도 빠르게 작동할 수 있을 거에요. 그리고 Backend는 Model의 역할을 하게 되요. 하지만 최신 SPA의 Fat Client는 클라이언트에서 다루는 기능이 많아져서 좋은 디바이스에서 유용하고, 서버의 부담이 적어요. 그리고 자체적인 상태관리도 하기 때문에 자체적인 MVC 아키텍처를 활용하고 있어요.

MVC 정리

MVC는 과거에 만들어진 아키텍처인 만큼, 복잡하지 않은 웹 앱을 만드는데 적당한 아키텍처에요. 왜냐하면 복잡한 웹 앱을 만드는데 쓰면, Controller가 엄청나게 많은 책임을 가지게 될거에요. 예를 들면 클라이언트 사이드에서 도메인별로 엄청 많은 로직을 구현해야 한다고 하면 관심사 분리가 더 중요해질 거에요. 그런데 MVC 아키텍처를 활용하면 Controller, View, Model에 이 기능들이 섞일거에요.

MVP 아키텍처

MVP 아키텍처는 Model-View-Presenter 레이어로 나뉘어져요. MVP는 MVC의 문제점인 View Layer가 가져야할 로직보다 더 가지는 문제점을 해결해요. MVC와의 차이점은 View layer가 Model layer로부터 직접적인 업데이트가 없다는 것이에요.

image.webp

MVP 패턴은 Passive View와 Supervising Controller 2가지로 나뉘어 져요.

MVP 아키텍처 - Passive View

image.webp

Passive View는 View 출력을 제외한 모든 로직은 Presenter에서 실행되요. 그래서 레이어간 경계가 확실하고 테스트하기도 쉬워요. 하지만 간단한 UI 구현에도 더 많은 코드가 필요하고, 인터렉션 구현에도 오버헤드가 발생해요.

MVP 아키텍처 - Supervising Controller

image.webp

MVC 패턴과 비슷한 방식이에요. 하지만 이 MVP 아키텍처에서는 간단한 UI 변경은 View에서 알아서 처리하고 복잡한 로직은 Supervising Controller를 통해서 처리하는 것이에요. 중요한 것은, Passive View와 다르게 View에서도 로직을 일부 가질 수 있다는 차이점이 있다는 거에요. Passive View와 비교해서 다음과 같은 장점을 가져요

  • 장점

    • View에서 직접 처리하는 작업들이 있을 수 있어서 위임하는 코드가 적고 개발 속도가 더 빠를 수 있어요.

  • 단점

    • View가 로직을 더 갖기 때문에 유닛 테스트하고 유지보수하기 더 어려워요.

MVP 아키텍처 정리

MVP 로직을 사용하면 레이어간 경계가 확실해져요. 그래서 테스트하기도 용히해요. 하지만 Presenter Layer가 너무 많은 로직을 가지게 되면서 테스트가 힘들어져요. 그리고 네트워크 통신등을 통해 적절한 동기/비동기 작업이 필요한 경우 동기/비동기 작업을 위한 로직을 분리하여 둘 공간이 없어서 이걸 해결하기 까다로워요.

MVVM 아키텍처

MVVM 아키텍처는 Model-View-ViewModel로 나누는 아키텍처에요. View Logic과 Business Logic을 나눠 관심사 분리를 하여 코드 유지 보수성과 용이한 테스트를 하기 위해서 만들어졌어요. Business 로직은 유저 이름 변경, 트랜잭션 생성, 장바구니 아이템 제거와 같은 로직일 것이고, View 로직은 모달 열고 닫기, 버튼 Disabled 상태로 출력, 다운로드 상태 변경과 같은 로직일 거에요. 간단히 말하면 실제 코어 데이터 변경을 하는 경우 비즈니스 로직, UI 만을 변경하기 위해 존재하며 실제 코어 데이터 변경을 하지 않는 경우에는 뷰 로직이라고 보면 되요.

image.webp

Vue의 MVVM 응용 예시(100% MVVM 일치하진 않지만, Vue는 MVVM에서 영감을 얻음.)

mvvm.webp

Vue는 양방향 데이터 바인딩을 통해 DOM의 입력 데이터와 Javascript 데이터를 동기화한다. 그리고 이 동기화 방식은 MVVM에서 영감을 받아서 만들게 되었다고 하며, 100% MVVM과 일치하지는 않는다고 한다.

MVP, MVVM 차이 코드 예시

사실 위에 그림만 봐서는 MVP와 다른점을 찾기 힘들 수 있어요. 단지 데이터 바인딩 같은 것이 추가되었다는 것을 알거 같아요. 하지만 그게 핵심으로 볼 수 있어요. 왜냐하면 MVP에서는 Presenter가 View를 알아야하고 View와 Presenter가 1:1 대응을 하지만 MVVM에서는 View와 View Model이 N:1 대응을 하니까요. 코드 차이로 볼까요?

// MVP, Presenter가 View 인터페이스를 알아야 함class UserPresenter {  constructor(view, model) {    this.view = view;  // View를 직접 참조    this.model = model;  }    async loadUsers() {    this.view.showLoading();  // 직접 호출    const users = await this.model.getUsers();    this.view.hideLoading();  // 직접 호출    this.view.showUsers(users);  // 직접 호출  }}// MVVM, ViewModel은 View를 전혀 모름class UserViewModel {  constructor(model) {    this.model = model;    // Observable한 데이터    this.isLoading = false;    this.users = [];    this.listeners = [];  }    // 상태 변경 시 자동으로 알림  notify() {    this.listeners.forEach(listener => listener());  }    subscribe(listener) {    this.listeners.push(listener);  }    async loadUsers() {    this.isLoading = true;    this.notify();  // View가 자동 업데이트        this.users = await this.model.getUsers();        this.isLoading = false;    this.notify();  // View가 자동 업데이트  }}

MVVM 아키텍처 정리

MVVM 아키텍처를 통해 UI(View), Business Logic(Model), Presentation Logic(ViewModel)이 명확히 구분할 수 있고, 테스트도 쉬워졌어요. 하지만 이해하고 개발하기 어려울 수 있고, MVP와 마찬가지로 API 요청같은 동기/비동기 로직 등의 코드 처리를 조직적으로 하기 힘들 수 있어요.

MVVM-C 아키텍처

MVVM-C 아키텍처는 MVVM 아키텍처에서 Coordinator가 추가된거에요. Coordinator가 추가된 이유는 MVVM만으로 URL 변경에의한 Screen Transition이나 Navigation 변경을 관리하기 힘들기 때문이에요. view는 단지 UI이고 Model은 데이터이며, View Model은 무슨 View가 오는지는 모르지만 binding을 해주는 것이기 때문에 Navigation Logic을 관리하기가 애매해져요.

image.webp

VIPER 아키텍처

VIPER 아키텍처는 View-Interactor-Presenter-Entity-Router 레이어를 말해요. MVP 아키텍처에서 Router가 추가되고, Model이 Interactor와 Entity로 분리되요. 이때 Entity대신에 Backend의 Service를 사용하기도 해요. 이 아키텍처는 기존에 API 데이터 Fetching을 조직적으로 관리하기 힘들었던 문제를 해결해 줘요. 또한 Router를 통한 Navigation 처리도 별도로 분리할 수 있게 되었어요.

이 아키텍처를 통해 관심사 분리를 더 철저하게 할 수 있고, 큰 프로젝트를 실 사용에 맞게 잘 구현할 수 있어요. 하지만 분리가 많이 되면서 코드양이 훨씬 많아져요. 원래라면 필요없는 boilerplate가 더 많아지는 거에요.

image.webp

Clean 아키텍처

CleanArchitecture.webp

이 아키텍처는 많이들 봤을거라 생각한다. 위 그림이 클린 아키텍처임을 말하지는 않는다. Layer가 더 많을 수도 있고 적을 수도 있다. 다만 적절한 기준의 분리를 통해 Layer가 나뉘어야 하고, 핵심 적인 Layer와 자주 변하는 Layer등 알아서 나눠야 하며, 핵심 가치인 entity와 외부 layer는 필수적으로 분리하는 것이 좋다. 그리고 위 그림은 기본적으로는 외부의 원은 내부의 원에 의존성을 가진다고 보면 된다. 반대로 말하면 내부에 있는 원은 외부의 원이 뭐든간에 신경쓰지 않아도 된다. 내부만 잘 알면 된다.

Hexagonal 아키텍처

헥사고날.webp

Hexagonal Architecture(또는 Prts & Adapters)는 애플리케이션의 핵심 도메인 로직을 외부 환경으로부터 분리하는 아키텍처 스타일이에요. 핵심 도메인 로직을 중심으로 바뀌는 외부환경에 대응가능하도록 만든다고 보면 되요.

이때 Driving Side를 애플리케이션의 동작을 요구하는 쪽이라 보면되고, Driven Side를 애플리케이션으로부터 명령을 받는 쪽이라 보면되요. 예를 들면, HTTP Controllers를 통해 요청이 오면 Driving이고, 데이터를 조작 요청을 통해 데이터가 바뀌는 DB를 Driven 영역이라고 보면 되요.

Screaming 아키텍처

image.webp

스크리밍 아키텍처는 Robert Martin에 의해 소개된 아키텍처다. 핵심은 top level-structure만 보고도 무슨 서비스를 하는지 알아볼 수 있어야 한다는 것을 의미한다. 위의 그림같이 아키텍처를 보고 바로 어떤 물건을 파는 어플리케이션이겠구나를 파악할 수 있어야 한다는 것이다. 폴더 구조도 Pages 하위에 UserProfile, Cart와 같이 폴더가 존재하면 바로 이해할 수 있지만, ReactComponents 폴더가 있고, HTTP 폴더가 있다고 하면 바로 알아볼 수 없을 것이다.(물론 폴더 구조에 집중 하라는 것이 아니라 아키텍처에 집중하라는 것이다. 풀더 구조는 언제나 바꾸기 쉽지만, 아키텍처는 그렇지 않다.)

Vertical Slices 아키텍처 (feat. FSD)

image.webp

Vertical Slices는 횡단 관심사를 종단으로 나누는 것으로 볼 수 있어요. (사실 그 역도 맞다고는 볼 수 있을 것 같다..?) 예를 들면 로깅, 보안, 트랜잭션의 횡단 관심사를 나눠서 기능 구현을 했는데, 너무 앱 규모가 커지면서 각 도메인별로 로깅, 보안, 트랜잭션을 구현하도록 팀을 나눠서 분배하게 되었다고 해봐요. 그럴때는 Vertical Slice를 통해 도메인별 관심사를 핵심 관심사로 두고 나눌 수 있고, 프로젝트 관리자도 도메인별로 팀에 일을 분배하고, 트래킹하기도 더 쉬워질 거에요.

FSD 아키텍처

fsd-folders-and-slices.png

프론트엔드 아키텍처중에 FSD 아키텍처라고 있어요. 앱을 구현하는데 여러 Layer로 나누고, 각 Layer에서도 또 도메인에따라 관심사별로 분리해서 사용해요. 그런데 보통 관심사 안에서도 api/model/ui 이런식으로 나뉘는데 그러면 또 내부적으로 모듈화되어 기능이 구현되는 것으로 볼 수 있어요. 이렇게 횡단, 종단 관심사가 체계적으로 나뉘어져서 재사용성도 매우 좋아지고 유지보수성도 좋아져요. 물론 아키텍쳐가 너무 복잡해지지만, 아키텍처를 사용하자는 책임자가 그림을 통해 뇌에 잘 인지시켜서 모두가 같은 아키텍처로 구현하도록 만들 수 있다면 큰 시스템을 만드는데 적절할 것 같아요.

...

마무리

저는 아키텍처는 최대한 경험 많은 시니어 개발자가 설계하고 구성원에게 무슨 의도로 설계했는지 공유하는게 좋다고 생각해요.

위와 같은 생각들을 한 이유는 주니어 개발자는 아직 코드레벨에서 구조를 만들면서 경험을 쌓고 있고, 내가 만든 코드가 전체 조직에 영향을 주지는 않기 때문이에요.(물론 시니어 개발자가 없으면 어떻게든 해내야죠!!) 그리고 전체 조직에 영향을 주는 만큼 경험 많은 시니어 개발자가 본인의 경험을 바탕으로 구현하는 소프트웨어에 적절한 아키텍처를 계속 제안해야한다고 생각한 거에요.

그리고 시니어 개발자는 구성원이 예측하고 생각하는 모델과 실제 개발하면서 나타나는 모델이 일치할 수 있도록 만들어줘야한다고 생각해요. 왜냐하면 단순히 아키텍처를 외우기 보다는, 자연스럽게 머리에 그려지는 것과 일치해야 전체적인 이해와 사용성이 좋아진다고 생각하기 때문이에요.

저가 너무 시니어 개발자에게 책임을 미뤘나요?? 그런데 저는 그런 시니어 개발자가 되고 싶어서 이렇게 말해봤습니다. 개발자는 책임있게 앞에서 끌어줄 수도 있고, 다른 개발자와 생각도 공유하면서 함께 더 나은 방향으로 나아갈 수 있는 힘을 얻는다고 생각해요. 그냥 아키텍처 혼자 만들고 혼자만 알면 뭐하나요? 본인의 의도, 생각을 공유하고 구성원과 발전해야 나도 발전하고 구성원도 발전할 수 있다고 생각해요.

특히나 경험적은 주니어 개발자에게 많은 도움이 될거라고 생각해요!! 저는 첫 직장에서 주니어 개발자들만 있어서 혼자 무엇을 공부해야하는지 찾아야했고 그 찾은 것을 공부해야했어요. 항상 방황하는 느낌이였어서 주니어 개발자가 저같은 시행착오 없이 같이 성장해서 더 좋은 제품이나 코드를 만들고 싶어요.

출처