MVC, MVP, MVVM
MVC
MVC에서는 Model이 데이터를 관리하고, View가 화면을 관리합니다.
그리고, Controller가 컨트롤을 담당하죠. Controller의 컨트롤 영역은 생각보다 넓습니다.
input도 받고, input에 대한 처리도 담당합니다. 결과를 화면에 표시할 수 있도록 데이터를 전달해주는 역할도 Controller가 맡는 일 중 하나죠. 모든 것을 Controller가 담당하고 있습니다.
고된 노동으로 인해 Controller는 점점 힘들어집니다.
MVP
MVP에서는 Controller 대신 Presenter를 사용합니다. Presenter는 화면에 어떤게 보여질지 처리하는 일을 담당합니다. Controller 역할을 하던 ViewController가 View의 역할을 담당하게 됩니다. 즉, 이전에 Controller가 담당했던 input를 받는 일은 View로 넘어가게 된거죠. 하지만, input이 들어오면 무조건 Presenter에게 알려주어야 합니다.
Presenter는 View로부터 알림을 받고서 로직을 처리합니다. 화면에 그리는 요소도 Presenter가 다시 알려줍니다. 즉, MVP는 MVC와 동일하지만 화면만 떼어 냈다고 볼 수 있습니다.
Presenter는 input, output에 대한 처리를 다 가지지만 UI 관련 로직은 가지지 않습니다. UI랑은 상관이 없기 때문에 Presenter는 Testable해지게 됩니다.
하지만, MVP도 문제점이 존재합니다.
View와 Presenter가 1:1이기 때문에 View 하나를 만들때마다 Presenter도 만들어줘야 한다는 점입니다. 비슷한 화면에 비슷한 로직을 지녔지만 Presenter를 매번 만들어야 하는겁니다.
MVVM
MVC에서는 Controller가 View와 Model 모두에게 시키는 일을 맡았습니다. MVP에서는 Presenter와 View가 서로에게 시키는 역할을 했습니다.
하지만, MVVM에 있는 ViewModel은 Model하고만 소통합니다.
ViewModel은 View에게 화면을 그리라고 시키지 않습니다. ViewModel은 View가 뭘 하든 신경쓰지 않습니다.
그럼 View는 어떻게 화면을 그리나요?
그리는 부분은 View가 가장 잘 알 겁니다. 따라서, View가 ViewModel를 지켜보는 겁니다.
그리고 ViewModel은 View에서 요청이 들어오면 그냥 값을 바꾸기만 합니다. 적절하게 데이터만 수정하고, View에게 어떻게 그리라고 알려주지 않습니다.
그렇기 때문에, 여러 화면이 있더라도 비슷한 데이터를 가진다면 전부 다 동일한 ViewModel를 공유할 수 있습니다. View와 ViewModel은 다대일 관계가 되는 겁니다. 이는 ViewModel이 특정 View에 대해서 뭘 그리라고 지시하지 않기 때문에 가능한 일입니다. 즉, MVP에서 Presenter가 View에게 시키는 부분을 제외한 것이 ViewModel입니다.
MVVM에서 View는 UI를 화면에 그리기만 하고 어떠한 판단도 하지 않습니다. ViewModel 값이 바뀌는 것만 바라보고 ViewModel에게 부탁하는 일만 합니다.
ViewModel은 어떤 화면을 그려야 하는지 View에게 알려주지 않습니다. ViewModel과는 상관없는 일이기 때문입니다. 그냥 input를 확인하고 처리하는 역할만 합니다.
Clean Architecture
애플리케이션을 구성하는 이 6가지 요소들에 대해서 얘기해보려고 합니다. 하나씩 뜯어가면서 살펴봅시다.
•
Model
Model은 Service의 원천이 되는 데이터입니다. 데이터는 바뀌지 않습니다.
•
Business Logic
Business Logic은 데이터를 처리하는 부분입니다. 데이터를 가공하는 로직을 가지고 있습니다.
이 부분도 바뀌지 않습니다. 비즈니스 로직이 가진 로직은 회사 정책과 관련된 부분입니다. 즉, 서비스의 근본에 대한 내용을 지니고 있습니다.
Model과 Business Logic를 묶어서 도메인 영역이라고 합니다.
이 부분은 회사의 사업 자체가 바뀌지 않는 한 동일합니다. 또한, 웹이든, 모바일이든 뭐든 모두 똑같은 형태를 가지게 됩니다. 어떤 데이터가 DB에 들어 있고 그 DB에 있는 데이터를 가공해서 어떤 로직에 의해서 어떻게 계산을 할 건지, 계산식이 동일하게 나옵니다.
•
ViewModel
ViewModel은 도메인을 가공해서 화면에 보여줄 수 있도록 처리하는 역할을 합니다.
Business Logic에 정의된 계산식에 의해서 계산한 결과를 ViewModel이 가지고 있게 됩니다. 결과를 동작 형태로 보여주기 위해 처리하는 역할을 합니다.
ViewModel은 중간에서 다리 역할을 하게 됩니다. 그래서 기능적, 역할적으로 interactor라고 하기도 합니다. interactor는 여러 뷰를 담당하고 있습니다. 즉, interactor가 가지고 있는 데이터는 특정 화면을 위한 데이터가 아니라 공용 데이터라는 겁니다.
특정 화면에서 특별하게 가공해서 보여줄 필요가 있다면 그 일은 ViewModel이 아닌 Presenter가 담당합니다.
•
View
화면에 보여주는 역할을 합니다.
•
Coordinator
네비게이션 처리를 합니다.
아키텍쳐를 튼튼하게 구성하고 잘 구성화하면 이 범주 안에 들어가게 됩니다. 전체를 봐도, 서비스만 봐도 이 범주 안에서 프로젝트가 구성되게 됩니다.
이를, 각각의 앞글자를 따서 V(iew)I(nteractor)P(resenter)E(ntity)R(outer)라고 합니다.
꼭 VIPER가 아니더라도 이런 역할을 하도록 구성하면 변경되는 요소가 어디냐에 따라서 유지할 수 있는 범위가 정리되고 제한되게 됩니다.
•
Entity - 회사가 바뀌지 않으면 영원히 변경되지 않는 부분
•
Interactor - 화면이 어떻게 바뀌든 바뀌지 않는 부분
•
Presenter - 디자인이 바뀌지 않는 한 바뀌지 않는 부분
회사에서 View 디자인을 바꾼다고 하면, Entity와 Interactor까지는 건드리지 않고 View와 Presenter 선에서만 코드를 수정하면 되는거죠. 변화에 유연하게 대처 가능합니다.
정말 Clean하지 않나요?
Clean Architecture는 변화가 발생했을 때, 어디까지 안전하게 지킬 수 있는가를 구조화한 것입니다.
Clean Architecture에서 원하는 바는 원 바깥에서 일어나는 일을 바깥에서만 처리하고 안에는 영향을 주지 말라는 겁니다. 즉, 우리가 위에서 말한 바와 동일하네요.
우리는 꼭 VIPER를 쓸 이유도 없고, MVVM를 꼭 쓸 필요도 없습니다. 상황에 맞게 사용하면 되는데, 결국 튼튼하게 구성하기 위해서는 구성 요소를 저렇게 하는게 좋습니다.
MVVM + Clean Architecture
화면에 그려지는 데이터인 Model은 여러 가지 형태로 변형됩니다.
어떤 식으로 변형이 되는지 예를 들어 보겠습니다. 서버에서 오늘 날짜 데이터를 가지고 와서 화면에 보여준다고 해봅시다.
서버에서 가져온 json 형식을 TimeModel 구조체로 변형해야 합니다. 구조체 안에서는 String 타입으로 변환해야 합니다. 이제 TimeModel 구조체를 가지고 와서 해당 값을 Date 타입을 변환합니다. 그리고 라벨로 보여주기 위해서는 다시 String 타입으로 변환해야 합니다.
TimeModel 구조체는 서버 모델입니다. 서버말고 DB에서 가져올 수도 있으니 Entity라고 부르겠습니다. 실제로 Model이라고 할 수 있는 거는 Date 타입으로 존재하는 모델입니다. 하지만, 화면에 보여주기 위해서는 Date가 아니라 String으로 변형이 일어나야 합니다. 실제 데이터 형식 모델로는 화면에 보일 수 없기 때문에 화면용 Model로 만들어야 합니다. 이게 ViewModel(화면모델) 입니다.
모델은 상황에 따라서 3가지 형태로 구성됩니다.
Entity(서버모델), Model, ViewModel(화면모델)
Entity가 로직에서 취급하는 Model이 되고 Model이 ViewModel로 변경되어서 화면에 보여지게 됩니다.
그럼 처음 서버로부터 들어오는 Entity는 누가 가져오는 걸까요?
서버로부터 데이터를 fetch해오는 역할은 Repository가 담당합니다. Repository의 결과물이 바로 “Entity” 입니다.
위에서 얘기한 것을 그림으로 정리해볼게요.
•
Repository
서버에서 Entity를 fetch해오는 역할을 합니다.
Entity를 전달하는 메서드를 가지고 있습니다.
•
Entity
Struct 형태의 모델입니다.
•
Mapper
Entity로부터 Model를 만들어내는 역할을 합니다.
•
Model
Mapper의 결과물입니다.
•
Logic
Model를 가지고 Logic를 수행합니다.
•
ViewModel
View를 위한 Model입니다.
결과 화면에 보이기 위해서는 Model를 ViewModel 형태로 변경해야 합니다.
•
View
ViewModel에 trigger를 만들어 놓고, ViewModel로부터 ViewModel(화면모델)를 가져옵니다.
화면에서 보여줄 데이터는 ViewModel에 있기에 ViewModel만 바라보면서 ViewModel의 데이터가 변경되면 화면에 세팅하는 처리를 해줍니다.
View에서 viewDidLoad 메서드가 호출되면 ⓵View는 ViewModel에게 trigger를 통해서 데이터를 요청합니다. 그러면, ⓶ViewModel은 Logic에게 데이터를 요청합니다. ⓷Logic은 Repository에게 데이터를 요청합니다.
⓸Repository를 통해서 Entity가 들어오면 Entity를 Mapper를 사용해서 Model로 변경하고 Logic으로 보내줍니다. ⓹ViewModel은 Logic에서 반환하는 Model를 ViewModel로 변경해서 View로 보내줍니다.
⓺View에는 ViewModel이 올라가게 됩니다.
앱의 비즈니스 로직은 ViewModel에 있지 않습니다. Logic에 존재합니다. 앱의 핵심 비즈니스 로직이기 때문에, View와 가까운 ViewModel이 아닌 Logic에서 관리해주는 겁니다.
MVVM의 의존 관계를 보면 View가 ViewModel에 의존하고 ViewModel이 Logic에 의존하고 Logic에서 나오는 Model이 Repository에 의존하고 Repository는 Entity에 의존합니다.
의존 화살표의 방향이 일관성이 있습니다. 자연스러운 방향이라는거죠.
MVVM말고 다른 아키텍쳐를 보면 화살표의 방향이 일관적이지 않습니다.
MVC는 Logic이 왕입니다. View 영역으로는 “이걸 가지고 그려라” 시킵니다. 도메인 영역으로는 “가져와라” 시킵니다. 즉, 모든 화살표가 Logic 영역으로부터 나오게 됩니다.
MVP는 MVVM처럼 일관적이지만 ViewModel의 역할을 하는 Presenter가 View에게 “이걸 그려라” 하고 시킵니다. 따라서, 화살표 방향에 일관성이 깨지게 됩니다.
“어떻게 나눌 것인가”, “어디에 어떤 코드를 위치시킬 것인가” 고민하는 것이 바로 아키텍쳐 입니다.
최대한 변경에 유연하고 방향이 일관적인 아키텍쳐를 만들도록 노력해야 합니다.