Android MVI패턴이란

SSY·2024년 6월 6일

Architecture

목록 보기
7/8
post-thumbnail

시작하며

아키텍처패턴은 변천사가 참 많다. MVC -> MVP -> MVVM에 이어 MVI패턴이다. MVI패턴을 잘 사용하기위해선 아키텍처 패턴의 변천사를 이해하는게 중요하다. 해당 포스팅에서도 MVC, MVP, MVVM패턴에 대해 간단히 소개하고 있지만, 의사코드와 함께 더 깊은 이해를 원한다면 아래 링크를 참고하길 바란다.

https://velog.io/@squart300kg/mvvmComplete

아키텍처 패턴의 변천사

[MVC]
Model View Controller로 이뤄진 이 패턴은 안드로이드의 View와 Controller가 거의 동일하다고 보면 된다. ViewSystem기준, View의 경우는 xml의 뷰를 의미하며 Controller의 경우는 ActivityFragment를 의미한다. 따라서 View와 Controller가 거의 같다고도 볼 수 있다.

Model의 경우는 비즈니스 로직이 정의되는 영역이다. MVC패턴 초기 개발시엔 Agent라는 클래스를 설계하고 이 곳에 비즈니스 로직을 정의하는 경우가 많았다. 또는 Activity or Fragment에 비즈니스 로직이 그냥 정의되는 경우가 많았다.

이는 아래와 같은 문제점들을 야기할 수 있다.

  1. 뷰와 비즈니스 로직의 강한 결합으로 인해 단위테스팅이 어렵다.
  2. UI갱싱 로직과 비즈니스 로직이 한 군데에 모여있는 경우가 많아 코드 가독성이 매우 떨어진다.
  3. 그 외 유지보수 비용이 많이 든다.

[MVP]
MVC패턴의 단점을 보안하기 위해 나온 패턴이다. 이는 하나의 View만의 전용 UI갱신 모듈인 Presenter를 둔다. 이를 통해 하나의 화면에서 UI가 어떤 이벤트를 받고 UI를 어떻게 갱신하는지를 파악하는데 좀 더 용이하다는 장점이 있다.

하지만 MVP패턴의 치명적인 단점은 View와 Presenter의 강력한 결합으로 인해 UI의 단위테스팅이 매우 어렵다는 문제를 갖고 있다. MVP패턴을 설계할 땐, Presenter인터페이스를 두고 이 하위에 View와 Presenter클래스를 설계하게 된다. 그리고 이들은 각각 ActivityPresenter클래스에 오버라이딩된다. 그 후, 이들은 상호 의존을 가지게 된다.

사용자의 이벤트를 Presenter에게 알리기 위해 View는 Presenter의 의존이 필요하며, 그 반대로 UI를 갱신하기 위해 Presenter는 View를 의존하게 되는 것이다.

이런 문제로 인해 View와 Presenter의 단위테스팅이 어렵다는 단점이 있다.

[MVVM]
MVP패턴과 거의 유사하다고 볼 수 있다. 하나의 View는 한개의 ViewModel을 갖게 된다. 이로 인해 MVP패턴처럼 ViewModel에서 한 화면의 사용자 이벤트를 받을 수 있고 이를 통해 UI를 갱신할 수 있다는 점에서는 매우 비슷하다고 볼 수 있다.

하지만 결정적인 차이는 단방향 데이터 흐름을 염두한 DataBinding을 사용하고 옵저빙 방식으로 UI를 갱신한다는 점이 큰 차이점이다. 이러한 아키텍처는 View는 ViewModel에 의존성을 갖지 않게 되며, 이로 인해 View와 ViewModel의 단위테스팅이 매우 용이해진다는 장점이 있다.

즉, MVP패턴과 MVVP패턴의 차이점은 아래와 같다고 생각한다.

단방향 데이터 흐름인 DataBinding을 사용하여 UI를 갱신한다. 이는 옵저빙 방식으로 UI를 갱신하며 ViewModel의 View를 참조하지 않음으로 인해, View와 ViewModel의 단위테스팅이 가능하다는 점이다.

그럼에도 불구하고, MVVM의 문제점?

MVVM패턴을 무지몽매하게 사용하는 입장에서 MVVM패턴의 단점을 생각해보진 않았다. 다만, MVI패턴을 공부하고나니 MVVM패턴의 단점이 보이기 시작했다.

앱이 커짐에 따라 화면이 복잡해진다. 그로 인해 어느 부분이 사용자의 이벤트인지, 그 이벤트에 따라 UI의 어떤 부분을 갱신하는지를 알기가 어려워진다는 문제가 있다. 이를 이해하기 위해선 앱을 클릭하며 하나하나 힘들게 코드를 읽으며 따라가야 한다.

하지만 MVI패턴은 이를 용이하게 해준다. 사용자가 어떤 이벤트를 발생시켰는지를 모델링하며, 그로 인해 화면의 상태가 어떻게 바뀔 수 있는지도 모델링을 한다. 게다가 이렇게 모델링한 클래스들이 하나의 파일에 들어감에 따라 코드를 읽으며 앱을 파악하는데에도 더욱 용이하게 된다는 장점이 있다.

이제부터 공부한 MVI패턴을 최대한 사실에 입각하여 정리해보려 한다.

MVI패턴?

MVI패턴은 Model + View + Intent의 조합이다. 우선 View는 이전 아키텍처패턴과 동일한 의미이다. 그 다음, Intent. 이 Intent는 안드로이드 프레임워크의 4대 컴포넌트의 통신객체가 아닌, 사용자의 행위(eg., 클릭 이벤트 등)를 의미하며 이들을 모델링한 것을 의미한다. 따라서 해당 UiEvent로 모델링된 인터페이스만 봐도 해당 화면에 어떤 사용자 이벤트가 있는지 확인 가능하다. (아래는 MVI패턴의 사용자 이벤트를 모델링한 코드이다.)

[UiEvent 인터페이스]

그 다음, Model이다. 기존 MVC, MVP, MVVM의 Model은 RestAPI or LocalDB 등 CRUD질의에 대한 요청/응답 클래스와 일련의 비즈니스 로직들을 의미했다. 하지만 MVI패턴의 Model은 UI에 집중한다.

MVI패턴의 Model은 ViewModel에서 사용자가 UI를 어떻게 구성할지를 2가지로 나누는데, 이는 UiStateUiSideEffect를 의미하며, 이들을 묶어 Model이라 한다.

우선 UiState는 화면에 지속적으로 보여줄 데이터를 나타내는 1개의 클래스이다. Android는 명령형 기반의 ViewSystem(=.xml)에서 선언형 기반의 Compose로 발전했다. 명령형 UI는 갱신 시, setText와 같은 갱신방식을 사용했다. 하지만 선언형 UI는 중심이 되는 1개의 UiState(늘어날 수 있음)를 관리하고, 이들을 composable함수에 옵저빙 방식으로, Argument를 주입한다. 이를통해, 개발자는 더이상 UI를 갱신하는 명령형 메서드(eg., TextView.setText())를 신경쓸 필요가 없는 것이다. 즉, MVI패턴은 중심이 되는 UiState만 잘 관리한다면, UI는 동기화될것이며, UI에 데이터가 지속적으로(eg., 리스트 목록들)보인다. (아래는 UiState를 모델링한 예시 코드이다.)

하지만 UI작업엔 '지속적'인 작업만 필요한 게 아닌, 1회성 작업도 포함한다. 예를 들어, 1회성 화면 이동, 1회성 토스트 메시지 로딩 등이 있다. 이러한 작업엔 UiSideEffect를 사용한다. (아래는 UiSideEffect를 모델링한 코드 예시이다.)

따라서 지속성이 필요한 UI작업과 1회성의 UI작업을 분리하여 모델링하는 것. 이것이 바로 MVI패턴의 Model이다.

UiEvent / UiState / UiSideEffect를 좀 더 간결히 정리하면 아래와 같다.

[UiEvent] : 사용자의 행동(eg., 클릭)을 모델링한 컴포넌트를 의미하며, MVI패턴의 'Intent'에 해당한다. 해당 컴포넌트를 통해 하나의 화면에서의 사용자 행동을 일목요연하게 확인 가능하다.

[UiState]
사용자의 UiEvent 트리거 및 앱의 비즈니스 로직이 실행실행되며, 최종적으로 UI를 갱신하게 된다. 이때, UI에 지속적으로 남아야하는 데이터라면 UiState를 사용한다. 이는 MVI패턴의 'Model'중 하나이다.

[UiEffect]
사용자의 UiEvent 트리거 및 앱의 비즈니스 로직이 실행실행되며, 최종적으로 UI를 갱신하게 된다. 이때, UI에 1회성 이벤트로 UI를 변경해야 한다면 UiSideEffect를 사용한다. 이는 MVI패턴의 'Model'중 하나이다.

참고

profile
불가능보다 가능함에 몰입할 수 있는 개발자가 되기 위해 노력합니다.

0개의 댓글