티스토리 뷰

시작하기에 앞서


제가 MVC, MVP, MVVM... 등등 어플리케이션 구조화를 위한 여러 디자인 패턴들을 학습하면서 나름대로 정리한 부분을 공유해보고자 합니다. 따라서 이 글에서 말하는 내용들이 정확한 사실이라기보단 제 나름대로 이해해보려 했던 노력으로 봐주시면 감사하겠습니다.

본 포스팅은 iOS를 기준으로 작성하였고 React를 조금 곁들였습니다. 하지만 그 외 플랫폼, 도메인에서도 어느정도는 이해하실 수 있을 내용입니다! 또한 앱, 웹 프론트 등을 합쳐 클라이언트라고 지칭하겠습니다.

 

1. MVC의 정의와 역사


MVC 등장 배경

MVC는 아주 오래된 패턴입니다. 1970년에 만들어져, 1980년대 쯤 보편화되었다고 하네요.

출처)https://www.researchgate.net/publication/248825145_A_cookbook_for_using_the_model_-_view_controller_user_interface_paradigm_in_Smalltalk_-_80

이때, 이 구조를 고안하게된 이유는 Small Talk으로 데스크탑앱을 만들기 위해서였다고 합니다. 콘솔에서 벗어난 GUI 개발이 시작되면서 화면을 그리는 로직이 생겨났고 데이터와 뷰의 책임 분리가 필요해진 것이죠.

 

이후 서버사이드의 정적 웹에서 서버/클라이언트 형식의 웹이 주로 만들어지게 되며 MVC는 각의 영역에서 사용되며 최적화되게 됩니다. 따라서 현대의 프레임워크를 위와 같은 구조 완전 그대로 이해해보려 하는 건 무리가 있을 수 있는 것 같습니다. 그래도 가장 비슷한 구조를 찾아보자면, 원래 서버사이드 렌더링으로 웹을 만들던 웹 프레임워크들(현재 백엔드 프레임워크)를 예로 들 수 있습니다.

 

MVC의 정의와 현대의 프레임워크/라이브러리

*MVC 각 요소의 역할
Model: 애플리케이션에 사용되는 데이터, 그리고 데이터를 변경시키는 비즈니스 로직
View: 사용자에게 보여지는 화면
Controller: 사용자의 입력을 받고, Model과 View를 연결하는 역할

다시 MVC 정의를 보고 형태를 생각해 보면 이런 느낌이 됩니다. User Action에 따라 Controller가 View와 Model을 매개하고 있습니다. 

 

백엔드 프레임워크

 

 

출처) https://chanhuiseok.github.io/posts/spring-3/

Spring 프레임워크와 같은 서버 프레임워크들은 위와 같은 MVC 형태를 사용합니다. Django도 MTV라 불리는 동일한 MVC형태를 채택하죠. 이제는 백엔드에서 담당했던 View가 프론트엔드로 분리되면서 대부분 백엔드에서 View 자체를 생성하지는 않지만, 서버사이드렌더링을 한다면 위와 같은 형태가 됩니다.

 

클라이언트에선?

하지만 클라이언트, 즉 프론트로 넘어오면 위와 같은 형태는 상당히 모호해지게 됩니다. 

 

 

2.  클라이언트의 MVC?


클라이언트 사이드(앱, 웹 프론트 등)에서 말하는 MVC패턴을 말할 때 가장 많이 사용되는 형태는 다음과 같습니다.

출처) https://developer.mozilla.org/ko/docs/Glossary/MVC

하지만 제가 경험한 클라이언트 프레임워크들에서 위와 같은 형태로 동작하도록 구현된 것은 거의 보지 못했습니다. (iOS, Flutter, React 등) 그럼에도 MVC라는 워딩은 많이 보였고, 보통 그 형태는 아래와 같았습니다.

 

iOS에서는 다음과 같은 형태를 MVC라고 많이 부르는 것 같습니다.

  • Model: 어플리케이션에서 사용하는 데이터 타입. 주로 Struct로 정의.
  • Controller: ViewController
  • View: 스토리보드 혹은 UIView를 구현한 클래스

React에서는 어떨까요? Flux패턴이 나오기 전, MVC 패턴에 대한 문제점을 언급하는 부분이 나오는데 사실 이 부분에 대해선 많은 논란이 있다고 합니다. React에서 MVC를 구현하고자 하면 어떻게 해야 할지 잘 떠오르지 않기 때문이죠.

  • Model: React 컴포넌트의 State
  • View: React 컴포넌트
  • Controller: ???

 

3. MVC의 문제: View와 Model이 강하게 결합되는 문제?


MVC자체도 이렇다 딱 정의된게 없으니 문제점을 정의하기도 어렵습니다만, 자주 언급되는 문제로 View와 Model의 강한 의존성, 양방향 의존성이 있습니다. View와 Model이 강하게 결합되며 어플리케이션의 복잡도를 올린다는 건데요, 제 생각에 이건 뭔가 아닌 것 같습니다(?)

 

위에서 언급한 방식을 보아도 보통 Model은 View나 Controller에서 단방향으로 의존하고 Model은 순수한 상태로 유지되기 때문입니다.

즉 강한 의존성이 있다거나, 양방향으로 의존한다거나 하는 경우는 아닙니다. 이런 문제가 나올 당시 개발 방식에서 MVC를 어떻게 정의했는지는 모르겠지만, 적어도 요즘 통용되는 MVC 패턴에서 위 문제는 발생하지 않는 것 같습니다.

 

공부하면서 어떤 문제 때문에 MVVM이라는 구조를 만든 것일까? 고민하면서 이 MVC의 문제점에 대해 생각해보았는데 제 결론은 위와 같았습니다.

 

더보기

그래도 어떤 경우에 이 문제가 발생할까를 고민해 보았을 때 다음과 같은 경우가 있습니다.

 

1. Model이 View나 Controlelr에 대한 의존성을 가지고 있는 경우

class CounterModel {

    private let counterView: ConterViewController?
    
    init(counterView: ViewController) {
    	self.counterView = counterView
    }
    
    private var count: Int {
    	didset {
            self.counterView.label.text = count
        }
    }
    
    func countUp() {
    	// ...
    }
}

 

이런식으로 Model이 View나 Controller 대한 의존성을 갖고 있는 경우 일거 같은데, 이건 데이터 타입의 재사용성을 너무 떨어뜨리기 때문에 실제 이렇게 하진 않았을 것 같습니다. 사실 MVP의 Controller에 가까운 형태인 것 같네요.

 

2. 어플리케이션 전역으로 하나의 모델 인스턴스가 사용되는 경우

모든 뷰에서 같은 모델 인스턴스의 참조를 사용하는 경우 하나의 Model의 변경사항이 여러 곳으로 전파되어 사이드 이펙트를 일으킬 수 있다는 점에서 복잡도 문제를 이야기할 수 있을 것 같습니다.

하지만 이 역시 보통 위처럼 각 Controller에서 인스턴스를 관리하는 방식으로 사용하기 때문에 실제 문제는 아닌 것 같습니다.

 

 

4. 데이터 바인딩의 등장, MVVM의 도입


그럼 MVVM이 왜 등장하게 되었을까 했을 때 제 생각은 '스타일링 코드와 비즈니스 로직의 분리'와 '데이터 바인딩의 등장'이라고 생각합니다. 뷰가 복잡해짐에 따라 단순 UI 스타일링을 정의하는 코드와 이벤트를 처리해 데이터를 변경시키는 부분에 대한 강력한 분리가 필요했을 것 같습니다. 특히 iOS에서는 VC가 View와 동시에 Controller역할을 하게 되어서 VC가 거대해지는 문제가 있기도 했죠. 여기에 더해 데이터 바인딩을 이용해 데이터와 뷰의 동기화가 편해지면서 더욱 MVVM이 자리잡은 것 같습니다.

 

데이터 바인딩이란, 데이터의 변경되면 View가 자동으로 업데이트되는 기술을 말합니다. 반대도 가능하죠, 사용자가 뷰를 변경시키면 데이터가 변경될 수도 있습니다.(TextInput 등) 그리고 이렇게 변경될 수 있는 데이터, 즉 변수를 상태라는 명칭으로 많이 불리게 됩니다.

 

이걸 가능하게 하는 새로운 컴퓨팅 기술이 탄생한 것은 아니고, 데이터 바인딩이란 개념이 등장하면서 이를 지원하는 프레임워크나 라이브러리가 생겨났다고 하는 것이 좋을 것 같습니다. iOS의 NotificationCenter나 KVO, didSet JS의 Proxy 혹은 콜백방식으로도 데이터 바인딩을 구현할 수는 있습니다. 즉, 옵저버 패턴만 구현할 수 있다면 어디서든 가능합니다. 하지만 모든 변수에 일일이 옵저버 패턴을 작성하는 것은 뷰 데이터에 매번 할당문을 써주는 것보다 더 귀찮은 방식이기 때문에 실제로 사용하기는 어렵습니다. 데이터 바인딩을 지원하는 프레임워크, 라이브러리들은 이런 귀찮은 코드들을 많이 추상화시켜 보일러플레이트코드를 없애주며 개발 편리성을 증가 시켰습니다.

 

iOS에서는 데이터 바인딩을 이용해서 더이상 VC가 모든 뷰의 상태를 관리하지 않고 다른 외부 객체(ViewModel)가 상태를 관리할 수 있게 함으로써 책임을 분리했습니다. VC는 UI를 그리는 스타일링 코드나 조금의 바인딩 코드를 가지게 되었고, 그 외에 프리젠테이션 로직이나 세부적인 바인딩, 상태 관리는 뷰모델이 맡게 되었죠. 그리고 이를 지원하는 라이브러리가 리액티브 프로그래밍을 이용한 RxCocoa(RxSwift)라이브러리 입니다. 또 애플에서 개발한 선언형 UI 프레임워크인 SwiftUI 또한 데이터바인딩 기능을 내장하고 있습니다.


 

한편, React에서는 MVVM을 잘 사용하지 않는데요, Custom hook을 사용해 useState와 함수 집합을 만들어 MVVM형태를 만들 수 있겠지만 모바일 처럼 뷰와 1:1로 대응되게 훅을 만들어서 사용하진 않는 것 같습니다. 대신 root view에 모든 책임을 두지 않고 세부 컴포넌트들에 비즈니스 로직을 나누면서 컴포넌트의 크기를 유지하는 것 같습니다. 한 화면에 담기는 기능이 모바일보다 많기 때문에 그런 것 같기도 하네요.

 

더보기

"SwiftUI에서 MVVM 사용을 멈추자"라고 생각이 들었던 이유

위 글을 읽고 생각해보았는데, 사실 루트뷰와 1:1로 대응되는 ViewModel을 두는 규칙은 편견인 것 같습니다! 뭔가 VC에 모든 로직을 넣어야했던 관습에서 굳어진 것일까요? 컴포넌트 분리를 하는 이상 하나의 루트뷰당 하나의 뷰모델이 존재해야하는 이유는 없는 것 같습니다. 리액트처럼 스타일링만이 존재하고 재사용되는 UI 컴포넌트를 디자인 시스템으로 정의하고, 실제 동작하는 컴포넌트들은 각각 비즈니스 로직을 가지고 있어도 되지 않을까요?

 

5.  잠깐,  MVP는?


분명..MVC -> MVP -> MVVM 형태로 발전했다고 했는데, MVP에 대한 언급이 없었죠? 사실 MVP는 MVC를 아주 약간 변형한 것이기 때문입니다.

 

출처) https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html

 

위 도표는 애플이 제안한 MVC인데요, 사실 구조를 뜯어보면 MVP와 똑같습니다. MVC를 공부하시다보면 애플 MVC가 따로 있다는 것을 알게 되실텐데, 이게 MVP입니다.

 

결론

'Model과 View의 의존성 문제를 해결한 방식이 MVP이다.'라고 하지만 똑같은 문제가 View에서 Controller로 옮겨갔을 뿐 해결된 것은 없어 보입니다. 그냥 iOS-UIKit 어플리케이션의 동작을 좀 더 잘 표현한 MVC가 MVP라고 생각하시면 될 것 같습니다.

 

7.  이젠 MVI!


그런데 또 새로운 패턴이 등장했습니다. 단순히 View와 ViewModel을 바인딩 하는 것을 넘어 '어떻게 잘 바인딩 할 것이냐'라는 문제로 넘어간 것인데요. 리액트의 FLUX패턴에서 말하는 단방향 아키텍처에 착안해 이를 해결하고자 한게 MVI입니다. (실제로는 cycle.js라는 라이브러리에 영향을 받았다고 합니다)

 

MVVM의 양방향 바인딩 문제

기존의 MVVM의 문제는 View와 ViewModel 양방향으로 바인딩 된다는 점이었습니다.

더보기

단, FLUX패턴에서 말하는 양방향 바인딩 문제와는 다른 문제입니다. FLUX패턴의 양방향 바인딩 문제는 '여러'뷰와 '여러'모델이 양방향으로 바인딩된다는 문제이고, MVVM에서의 양방향 바인딩은 '하나'의 뷰(Root View)와 '하나'의 뷰모델에 대한 문제입니다.

View에서도 ViewModel의 State를 변경하고, ViewModel에서도 State를 변경하게 되는데요 이는 한 뷰의 상태가 많아질수록 어떤 상태를 어디서 변경할지 예측하기 어렵게 만드므로 어플리케이션의 복잡도를 올리게 됩니다.

 

따라서 MVVM패턴에서는 Input State과 Output State를 구분하는 방식을 사용함으로써 이 문제를 해결하는 경우가 많습니다.

class XXViewModel {
    struct Input {
        let viewDidLoadEvent: Observable<Void>
        let buttonTapEvent: Observable<Void>
    }
    struct Output {
        var name = BehaviorRelay<String>(value: "00님")
        var error = BehaviorRelay<String>(value: "")
    }
    
    // ...
}

이런식으로 많이 사용하죠. (살짝 단방향 느낌이 나네요)

 

SwiftUI같은 경우는 좀 더 복잡해집니다.

// View
struct ContentView: View {

    @StateObject var viewModel: ContentViewModel
   
    var body: some View {
	// ...
                DefaultButton(buttonSize: .large, buttonStyle: .filled, action: {
                    viewModel.count += 1 // viewModel State 직접 접근
                    viewModel.countup() // viewModel 함수 직접 호출
                })
	// ...

Combine의 Subject를 사용하지 않는다면 이런식으로 View에서 직접적으로 State를 변경하거나, User Action에 따라ViewModel의 내부 함수를 직접 호출해야합니다. 이 경우에 실수가 발생하면 루트뷰와 그 하위 컴포넌트들 어딘가에서 잘못된 값을 넣지는 않았는지, 함수를 잘못 호출하지는 않았는지 일일이 찾아야합니다.

 

 

단방향 바인딩

이를 해결하기 위해 추가적인 기능을하는 요소들을 도입해 양방향 바인딩을 단방향으로 바꾼 것이 MVI입니다.

 

보시면 View->Intent->View로의 단방향 흐름이 형성되었죠. View는 Intent의 내부 메서드나 state에 직접 접근하지 않고 UserAction에 따라 지정된 Action을 발행해 Intent에 전달합니다. 그러면 Intent 내부의 Reducer가 이를 받아 액션따라 state를 변경하고, 데이터 바인딩에 따라 자동으로 뷰가 변경됩니다. 

 

MVI의 장점 (MVVM에 비해)

이 흐름으로 인해 View가 Intent내부의 로직(function)아예 모르게 되고, 직접적으로 state를 변경하지 않음으로써 둘 간의 결합도가 많이 떨어지게 됩니다. 또한 View에는 UI코드만 작성되고 다른 로직들은 모두 Intent에서 처리하는 것이 강제되어 책임분리가 더욱 명확해집니다. (기존 MVVM은 state 변경 로직의 위치를 강제하는 부분이 없습니다.)

 

MVI 라이브러리

이를 도와주는 iOS 라이브러리들은 ReactorKit이나 SwiftUI에서 많이 쓰이는 TCA등이 있죠. 요즘은 대부분 이 라이브러리들을 도입하는 것 같습니다. 추가적으로 이 라이브러리들은 MVI아키텍처에 함수형 프로그래밍(Immutable, Pure function, Recursive)등을 이용해 프로그램을 더욱 안전하고 확장성 높게 만들줍니다.

 

React Flux(Redux, Recoil)와 MVI의 차이

마지막으로, React Flux와 MVI의 차이를 알아보겠습니다. MVI는 Flux에 영향을 받았을 뿐, 완전히 같은 구조는 아닙니다. (사실 MVI라는 명칭의 핵심 개념은 단방향 아키텍처를 의미하는 것이므로 FLUX와 동일한 개념이 맞지만 여기서 말하는 MVI는 앱 개발에서 주로 사용하는 방식입니다!)

 

React는 중앙집중식관리 MVI는 지역적 관리

가장 중요한 차이점은 State의 관리 방식의 차이입니다. View와 Intent는 보통 MVVM과 같이 RootView-Intent와 1:1관계를 가지고 Intent는 각각 지역적인 State를 갖습니다. 그러나 React Flux에서는 State를 전역적, 즉 중앙집중식으로 관리합니다. Flux패턴을 적용한 라이브러리들을 이용해서 만든 State 집합을 Store라고 하는데요, 이 Store는 리액트 어플리케이션 1개당 1개가 권장됩니다. 즉, 모든 뷰가 같은 State 인스턴스를 참조할 수 있는 것입니다. 따라서 리액트는 '여러' View와 '하나'의 Store가 의존하고, MVI에서는 '하나'의 View와 '하나'의 Intent가 의존하므로 둘은 다른 매커니즘으로 동작하게 됩니다.

 

+ 그 외 아키텍처


MV*은 View에 관한 패턴

이 부분은 처음에 공부할때 헷갈렸던 부분이라 추가했는데요. 실제 앱에는 상태를 변경하는데 필요한 네트워킹, 로컬 DB 접근, 그외 로직 등 다양한 부분이 포함되어 있죠. 이런 부분들은 MV*에서 어디에 포함되어야할까요?

 

MV*는 View와 상태(State)에 대한 논의입니다. 즉, View와 Controller(였던 것)을 어떻게 구성해야 뷰의 상태를 잘 관리할 수 있을까? 라는 접근으로 탄생한 패턴들이기 때문에, 전체 앱에서 일부에 관한 내용입니다. 따라서 위와 같은 로직을 구조화하려면 또 다른 아키텍처 컴포넌트들이 필요합니다.

 

그럼 비즈니스 로직은 어떻게 분리해야할까?

비즈니스 로직을 분리하는 방법은 무궁무진합니다. Service, Provider, Manager, Client라는 이름으로 구현되거나, 클린아키텍처를 본따 Usecase, Repository등으로 나누기도 합니다. 이 중에서 더욱 자주 사용되거나 복잡하다면 모듈이나 라이브러리로 분리될 수도 있죠. 따라서 비즈니스 로직을 담당하는 컴포넌트는 어플리케이션의 성향에 따라 적절히 분리하는 것이 중요합니다. 복잡한 비즈니스 로직을 처리해야한다면 더 세세하게 책임을 분리해야해죠. 그래도 일반적으로 소규모 앱에서 사용하는 방식을 꼽아보자면 기본적인 네트워킹을 해주는 Netowrk layer를 두고 Service에 세세한 로직을 담는 방식이나 클린아키텍처를 사용하는 것 같습니다. 

 

VIPER, RIBs

추가적으로 iOS에는 VIPER와 RIB등의 패턴도 있습니다. MV* 아키텍처에 조금 더해서 화면 전환(Route)에 대한 컨벤션을 정한 아키텍처입니다. 둘 다 해당 아키텍처를 따를 수 있는 템플릿이나 라이브러리를 제공합니다. 하지만 요즘에는 자주 사용하지는 않는 것 같습니다. 제 생각엔 라우팅에 관한 것은 위 아키텍처들의 아이디어를 참고해서 프로젝트에 맞게 녹여내는게 좋은 것 같습니다.

 

 

 

✅ 결론
- MVC는 여러 도메인에서 적용되는 기본적인 패턴이지만, 다양하게 변화해와서 원래의 개념과 딱 맞지 않다.
- View-Model/View-Controller간의 의존성 문제가 아닌, 데이터 바인딩의 등장과 스타일링코드/로직코드를 분리하기 위해 문제로 MVVM이 등장하였다고 생각한다.
- MVVM의 양방향 바인딩 문제 때문에 단방향 흐름을 가진 MVI 패턴이 도입되었다.
- 앱에서 주로 사용되는 MVI 패턴은 FLUX패턴과 비슷한 원리로 동작하지만 세부 동작은 다르다.
- MV*패턴 시리즈는 비즈니스 로직(Model)에 대한 논의가 아니라 View와 State에 관한 논의이다. 따라서 여기에 다양한 아키텍처를 더해 사용할 수 있다.

 

 

마치며


제가 아키텍쳐 패턴을 처음 접했던 때부터 지금까지 고민했던 내용과 결론을 집합해보았습니다. 저는 앱 개발을 주로하지만 웹 개발에 대한 관심도 많은데요, 두 영역을 같이 공부하다보니 다른듯 같은듯 어떤점이 다르고 어떤점이 같은지 비교하는 재미가 있었습니다.

 

패턴 공부하시는 분들께 도움이 되었으면 좋겠습니다.

 

읽어주셔서 감사합니다!

 

ref.

곰튀김님의 MVVM 강의

iOS 아키텍처 패턴(MVC, MVVM, VIPER)

MVC, 정말제대로알고계신가요?

MVC의 발전 역사

아키텍쳐 패턴 (MVC, MVP, MVVM)

프론트엔드에서 MVC보다 더 많이 쓰이는 패턴은 ?

MVC 창시자가 말하는, MVC의 본질

MVVM 디자인 패턴이란

+ ChatGPT

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함