안녕하세요! 저는 펜타시큐리티의 아이사인 개발부 IM팀에 재직중인 송상윤이라고 합니다. 이번에 작성할 포스팅은 펜타시큐리티의 대표 모바일 제품인 iSIGN+ Pass v2의 아키텍처를 성공적으로 끝마치기까지의 여정입니다. 현재 안드로이드 개발자로 재직중이시고, 회사 제품의 개선 및 개인의 성장까지, 2마리의 토끼를 잡으시려는 분에게 아주 유용할 수 있다고 감히 말씀드려봅니다!
이번 포스팅에선 iSIGN+ Pass v2가 어떤 아키텍처 패턴을 채택했고, 그 이유까지 알아볼까 합니다. 그럼 시작하겠습니다!
iSIGN+ Pass v2의 최종 목표는 테스트 자동화이며, 그러기 위해 아래 사항들이 선행되어야만 합니다.
따라서 결국, 어떤 아키텍처를 선택하느냐가 모바일 테스트 자동화를 완료시키는데 중요한 핵심 Key라고 판단이 되었습니다. 그렇다면 안드로이드 아키텍처에는 무엇이 있을까요?
안드로이드 아키텍처는 수많은 변천사를 거쳐왔습니다. 대표적으로 MVC -> MVP -> MVVM -> MVI 순으로 알고있죠. 그리고 각기 아키텍처 패턴들은 장단점을 보유하고 있는데요. 해당 아키텍처들이 어떤 특성이 있는지 개괄적으로 알아볼까 합니다. 또한 그 특성을 읽어내려다시다 보면, 선택의 이유 또한 납득이 되실거라 생각합니다 :)
UI에 해당하는 로직과 비즈니스 로직이 하나의 Activity
or Fragment
에 밀집되는 아키텍처 패턴입니다. 이는 UI관련 로직이 하나의 화면에 모두 밀집되기에, 추후 유지보수가 매우 불편해질 수 있다는 단점이 있습니다. 하지만 간단한 앱을 만들 경우는 생산성이 빠를 수 있다는 장점이 있죠. 하지만 요즘 구글 스토어를 보시면 아시겠지만 대부분의 앱의 규모가 크며, 해당 앱들은 MVC패턴으로 작성하기에 난해해 보입니다. 즉, MVC패턴으로 큰 규모의 앱을 만들기에는 추후 하나의 Activity
or Fragment
에서 UI로직과 비즈니스 로직을 처리해야하기에 큰 어려움이 있다는 것입니다.
하지만 MVC패턴이 꼭 Activity
or Fragment
에 비즈니스 로직까지 포함되지는 않는 경우가 있는데요. MVC패턴의 불편함으로 인해, 하나의 View
에 하나의 Agent
모듈을 1:1매핑하고 그 로직에 비즈니스 로직을 작성하는 경우도 있습니다. 그러기에 1 View : 1 Agent모듈을 매핑하는 패턴에 착안하여 나온 패턴이 MVP패턴입니다.
이는 1개의 View
에 해당하는 로직과 1개의 비즈니스 로직 모듈인 Presenter
를 매핑시킨 패턴입니다. 예를 들어, MainActivity
가 있다면 MainPresenter
가 매핑되는 방식이죠. 그 후, View
와 Presenter
는 상호 의존 관계를 맺게 됩니다.이 방식은 약 2019년도까지 많이 사용하는 패턴이기도 했습니다. 어쩌면 이 패턴이 1개의 View
와 1개의 Presenter
를 매핑하기에 좀 더 깔끔한 패턴이라고 볼 수 있습니다. 하지만 문제가 존재하죠.
앱의 규모가 커지면 어떤게 필요할까요? 바로 테스트입니다. 이러한 테스트를 잘 하기 위해선, View
가 Presenter
를 단방향으로 의존해야 합니다. 그러지 않을 경우, Presenter
의 단위테스트를 진행할 때, View
의 참조성으로 인해 해당 View
를 호출해야만 하는 문제가 생기죠. 따라서 Presenter
가 View
의 의존성을 끊어내고자 하는 패턴이 생기게 됩니다. 이 패턴이 바로 MVVM패턴입니다.
MVP패턴의 치명적인 문제점을 한 문장으로 요약하자면, '비즈니스 모듈이 UI에 의존성을 가진다'입니다. 따라서 MVVM패턴은 이 의존성을 끊어내고자 합니다.
MVVM패턴 또한 MVP패턴과 유사하게 1개의 View
와 1개의 ViewModel
을 배치시킵니다. 하지만 결정적인 차이가 있는데, ViewModel
내에서 View
를 변경시키고자 할 때, 명령형
방식이 아닌 옵저빙
방식으로 View
를 갱신합니다. 즉, View
에 ViewModel
의 스트림 객체를 참조 및 옵저빙하고, ViewModel
이 변경사항을 발행하면, View
에선 해당 값을 수신받아 View
를 갱신하는 방식입니다.
이로 인해 ViewModel
의 단위 테스트가 간편해집니다. ViewModel
은 View
를 일절 참조하지 않기에, ViewModel
객체를 생성해 단위테스트를 진행하고자할 때, View
객체를 알 필요가 없어지는 것이죠. 그러기에 대규모 앱을 만드는데 있어 현재까지도 MVVM패턴이 널리 쓰이기도 합니다. 하지만 또 다른 문제가 발생합니다.
앱이 커지게 되었을 경우, ViewModel
의 메서드 또한 늘어나게 됩니다. 그러기에 네이밍 정의에 어려움과 혼재가 생기며 유지보수가 어려워지게 됩니다.
또한 android에선 선언형 UI인 Compose
가 도입되었습니다. Compose
는 UI의 State Holder Class
를 보유하고 이 클래스의 변경사항을 옵저빙받아 UI가 자동 갱신되도록 하는 방식에 초점이 맞추어져 있습니다.
물론, MVVM패턴과 Compose를 조합하여 충분히 개발이 가능합니다. 하지만, 규모가 커짐에 따라, ViewModel
은 State Holder Class
를 보유해야하며, View
로부터 사용자 이벤트를 받고, 그에 따른 작업 처리를 해주어야 합니다. 이때, 필요한 작업처리는 크게 2가지로 나눠질 수 있는데, State Holder Class
를 갱신하여 UI를 변경하거나, State Holder CLass
를 변경하지 않고 1회성으로만 UI를 변경하려는 경우가 생기게 됩니다.
즉, MVVM패턴의 경우, ViewModel
에서 할 수 있는 2가지 행위가 구분지어지지 않음에 따른 코드 유지보수성의 문제가 대두됩니다. 이는 곧 UiEvent
와 UiSideEffect
를 구분짓는단 뜻입니다.
MVI패턴은 MVVM패턴에 착안하여 만들어진 만큼, MVVM패턴과 유사성이 많습니다. 하지만 선언형 UI인 Compose
가 떠오르고 그에 따른 State holder Class
의 보유, UiEvent
/UiSideEffect
를 구분지어 처리하고자 하는 것이 MVI패턴만의 차별점 입니다. 또한 MVI 패턴으로 개발을 하다보면 ViewModel
내, UiEvent
/UiSideEffect
를 분리해 처리하는 과정에서 비슷한 패턴이 반복되는 경우가 많은데요. 해당 패턴이 보일러 플레이트 코드라고 보여질 수 있습니다. 하지만 이 둘을 확실히 분리하고 의존도를 없앨 수 있다는 점이 큰 장점이라 채택이기도 합니다 :)
iSIGN+ Pass v2는 MVI
패턴으로 마이그레이션이 되었습니다. 즉, 선언형 UI인 Compose
를 채택하였고,State holder Class
의 관리를 보다 간편하게 하고자 합니다. 또한ViewModel
내의 UiEvent
/UiSideEffect
를 구분지음으로써 UI갱신 방식을 적절히 분리하고자 하였습니다. 마지막으로 MVVM패턴의 장점인 테스트 용이성도 계승받아 편한 UI Test/Unit Test가 가능한 것이 해당 패턴을 채택한 큰 이유이기도 합니다.
단순 사이드 프로젝트로 앱을 만든다면 MVC
패턴으로 만들어도 됩니다. 정답은 없으니까요. 하지만 앱이 커질 것을 예상하고 그에 맞게 대비하는 것 또한 중요합니다. 아래 이미지처럼 미래 변경사항을 예상하고 적절한 모듈을 적절한 곳에 배치하는 것이 바로 아키텍처라 생각듭니다 :)
지금까지는 iSIGN+ Pass V2가 마이그레이션을 진행하며, 어떤 아키텍처 패턴을 채택했는지, 왜 해당 패턴을 채택했는지를 알아보았습니다.
다음 편도 기대해주시면 감사하겠습니다 :)