안드로이드 아키텍처는 많은 과정을 거쳐왔습니다. 그 중, 대표적으로 MVC, MVP, MVVM가 있고, 이 아키텍처들의 개념과 장단점과 흐름에 대해 알아보고자 합니다.
(MVI는 시간관계상 제외)
아키텍처 변천사를 설명드리기 전, 안드로이드의 기본적인 UI 구성 방식을 알아보고자 합니다. 안드로이드는 기본적으로 아래의 컴포넌트들로 UI를 구성합니다.
Activity
: 객체 지향 언어로 작성 된 클래스이며 xml
ui 스크립트를 참조layout.xml
: 마크업 언어로 작성된 스크립트이며, ui를 의미또한 참조된 컴포넌트는 위 사진과 같은 순서로 동작합니다. 첫 번째로, Activity
에서 layout.xml
내, 하나의 View 위젯을 참조합니다. 그 후, 해당 위젯을 통해 onCreate
라는 메서드 내에서 해당 컴포넌트 값을 읽어들이거나 변경하는 등의 작업을 합니다.
MVC패턴은 Model
+ View
+ Controller
가 합쳐진 말로, 각 어원은 아래와 같습니다.
Model
: API 통신을 포함한 비즈니스 로직과 이에 사용되는 데이터 모델 컴포넌트View
: UI 컴포넌트Controller
: Model
로부터 상호작용한 결과를 View
에 보여주기 위한 중계 컴포넌트이에 따라, 각 컴포넌트의 의미를 설명드려보자면, 우선, Model
에 해당하는 컴포넌트는 원격 또는 로컬 API와 통신하는 비즈니스 로직과 이에 해당하는 데이터 모델입니다.
또한 Controller
에 해당하는 컴포넌트는 Activity
이며, 이곳에서 Model
과 View
를 중재하여 UI에 데이터를 뿌릴 준비를 합니다.
또한 View
의 컴포넌트는 layout.xml
이며, Controller
로부터 데이터를 받아, 사용자에게 데이터를 보여주게 됩니다.
하지만 MVC아키텍처 패턴의 가장 큰 문제는 데이터를 가공하는 비즈니스 로직과 UI를 바인딩하는 로직이 분리되지 않았다는 점입니다. 작은 앱을 만들 때엔 상관 없겠지만, 큰 앱을 만들 땐 문제가 됩니다. 만약 위와 같은 기능이 점점 늘어난다면, 하나의 화면에 1,000줄은 금방 넘어가기도 합니다.
따라서 이러한 문제점을 해결하기 위해 마틴 파울러에 의해 MVP패턴이 제시됩니다.
이 패턴은 특정 UI에만 한정되는 비즈니스 로직을 분리하자는 취지로 나왔으며, MainActivity
에 해당하는 비즈니스 로직은 MainPresenter
, DetailActivity
에 해당하는 비즈니스 로직은 DetailPresenter
와 같이 각 모듈을 1:1 매핑하는 방식으로, UI의 비즈니스 로직을 분리시킵니다. MVP패턴의 구조를 간단하게 살펴보면 아래와 같습니다.
우선, 기존의 Activity
는 위와 동일하게 존재합니다. 다만 차이는 아래와 같이 MainContract
클래스 내, Presenter
와 View
라는 인터페이스가 정의된다는 점입니다.
그리고 MainContract.Presenter
인터페이스는 MainPresenter
에, MainContract.View
인터페이스는 MainActivity
에 오버라이딩 됩니다.
위와 같이, Presenter
와 View
가 분리됨에 따라, 아래와 같이 비즈니스 로직과 UI 로직이 분리된 것을 볼 수 있습니다.
이러한 구조에 따라 MVP패턴의 구조와 흐름은 아래와 같이 묘사할 수 있겠습니다. 말씀드리기 앞서, MVP패턴과 MVC패턴에서 각 컴포넌트들이 포함하고 있는 요소들에 변화가 생기게 됩니다.
우선 View
에 해당하는 컴포넌트는 Activity
, layout.xml
, MainContract.View
인터페이스를 포함하고 있으며, Presenter
에 해당하는 컴포넌트는 MainContract.Presenter
인터페이스를 포함하고 있습니다.
우선, 사용자가 UI에서 MainPresenter
로 이벤트를 요청합니다. 그 후, Model
컴포넌트에게 API 요청 등의 작업을 통해 필요 데이터를 가져옵니다. 그렇게 가져온 데이터는 MainPresenter
에 전달되며, 이는 곧 다시 MainContract.View
인터페이스를 통해 Activity
로 전달됨과 동시에 UI 로직의 실행 및 UI를 최종적으로 보여주게 됩니다.
[중복 비즈니스 로직의 문제점]
다만, 이는 단점이 존재합니다. MVP패턴은 UI에 해당하는 비즈니스 로직을 각각의 Presenter
로 분리한다는 장점을 가졌지만, 앱이 커짐에 따라, MainContract
내부가 방대해지고 관리가 어려워진다는 점이 있습니다. 또한 MainContract
는 UI와 1:1매핑관계이기 때문에 해당 Presenter
를 재활용할 수 없어 비즈니스 중복코드가 늘어난다는 단점이 존재합니다.
[비즈니스 로직 단위 테스팅의 문제점]
또한 비즈니스 로직 단위 테스트를 진행할 때에도 문제입니다. 단위 테스트 진행을 위해선 UI 의존성이 없어야 하는데요. MainPresenter
내부를 보면 MainContract.View
를 의존하고 있는걸 알 수 있고, 이는 비즈니스 로직이 UI에 의존한다는 문제가 존재합니다. 즉, 순수 비즈니스 로직 테스트를 진행하는데 있어, UI에 의존적이라는 의미고 이로 인해 단위 테스팅이 어렵게 됩니다.
만약, 단위 테스팅을 진행할 시의 코드 형상은 아래 그림과 같이 나오게 됩니다. 즉, 테스트 시작 지점과 결과 수신 지점이 달라지게 되고, 이로 인해 코드 추적이 어려워 집니다. 코드 추적을 위해선 결국, 내부 구현이 어떤지까지 타고 들어가야하는 문제가 발생합니다.
비즈니스 로직의 중복과 단위 테스팅이 MVP패턴의 문제라는 점을 알아봤습니다. 이에, MVVP패턴은 이러한 단점을 보완하기 위해 나온 아키텍처 패턴입니다.
[비즈니스 로직 중복 문제의 개선]
이전 MVP패턴은 MainContract
라는 모듈을 통해, Presenter
와 View
를 내부에 사전 정의해야 했습니다. 이로 인해 1:1 관계가 매핑되었죠. 하지만 MVVM패턴은 이러한 작업을 진행시키지 않아도 됩니다. 그 핵심은 바로, 옵저버 패턴의 UI 상태 관리 모듈에 있습니다.
이 패턴을 알기 위해선 MVP패턴의 UI갱신 방식이 함수의 호출(=명령형)이었다는 점을 상기할 필요가 있겠습니다. MainPresenter
내에서 MainContract.View
에 정의 된 객체를 직접 참조해서 메서드를 호출하고 있었습니다.
하지만 MVVM패턴은 옵저버 패턴의 상태 관리 모듈을 사용하여 UI를 관찰하는 방식으로 갱신합니다. 우선 Activity
내에 ViewModel
을 정의합니다. 그 후, ViewModel
의 메서드를 호출하게 되면, 내부적으로 API를 호출하고 결과 값 수신 시, ViewModel
내부에 정의 된 상태 관리 모듈인 StateFlow
에 값을 업데이트하고 있음을 알 수 있습니다. 이때, layout.xml
은 이러한 상태 관리 모듈을 사용하여 값을 통지받기를 기다리고 있습니다. 이에, 상태 관리 모듈에 값이 할당되면, 옵저버 패턴방식으로 값을 최종적으로 layout.xml
로 값을 전달해주고 이를 통해 순수 UI로직을 실행시켜주게 됩니다.
이를 통해 알 수 있는 점은 ViewModel
은 View를 참조하지 않는다는 점입니다. 따라서 ViewModel은 View로부터 자유로우며, 이를 통해 알 수 있는 점은 View와 ViewModel은 1:N 관계라는 점입니다. 그러므로 만약 비즈니스 로직의 중복 문제가 발생한다면, ViewModel을 여러 View에서 재활용 함으로써 비즈니스 로직의 중복을 줄일 수 있게 됩니다.
[비즈니스 로직 단위 테스팅의 문제점]
ViewModel은 View로부터 자유롭다고 말씀드렸는데요. 이는 ViewModel이 View에 의존도가 없기 때문에 ViewModel만 똑 떼어서 단위 테스트를 진행하기가 더 수월해지게 됩니다. 또한 ViewModel은 View에 정의 된 메서드와도 독립적이기 때문에 추가적인 메서드 오버라이딩이 필요 없으며, 하나의 메서드 안에 기능 테스트를 진행할 수 있게 됩니다.