마이리얼트립에서 사용하는 iOS 개발 아키텍처

MVVM을 사용하면서 데이터의 관리와 디자인요소를 어떻게 처리하는가

Woo JeongGeun
How we build MyRealTrip

--

모두들 좋은 구조를 짜기 위해 고민을 거듭합니다

Step 1. 들어가며

마이리얼트립 모바일개발팀에서 일하고 있는 우정근 입니다.
여러분들은 어떠한 앱 아키텍처를 사용하고 계신가요? 저는 이 글에서 마이리얼트립 iOS 개발팀에서 고민을 하고 저희에게 맞도록 개선해 나간 앱 아키텍처에 대해 이야기하고자 합니다.

마이리얼트립의 iOS 개발자들도 많은 고민을 하였습니다. 이미 과거에 MVC, MVP, MVVM 등의 아키텍처로 개발을 해온 경험이 있었고 VIPER에 대해서도 익히 들어왔었죠. 마이리얼트립의 여행자앱은 초기에는 MVC로 개발되었습니다. 앱 개발을 위한 일정은 짧았고 급했으며 개발을 위한 시간은 많이 할애하기 어려웠습니다. 따라서 적합한 아키텍처를 리서치하고 이를 완벽하게 적용하기 어렵다고 판단하였고 가장 익숙했던 전통적인 MVC 개발패턴으로 진행을 하여 초기 버전을 출시하였습니다. 이는 겉으로 보기에는 성공적으로 느껴졌습니다. (앱은 크래쉬 없이 잘 동작하였고 예쁜 디자인으로 잘 포장되어 있었으니까요.)

그리고 많은 업데이트가 있었습니다. 새로운 Feature들의 추가 개발, 수많은 버그 수정, 그리고 새로운 iOS 개발자의 영입에 따른 통일되지 않은 컨벤션….다들 겪어보셨겠죠? 시간이 흐를수록 코드는 난잡해지고 오염되어 갔으며 이에 따른 사이드이펙트들이 발생하기 시작했습니다.

지쳐갑니다..

이대로 계속 개발을 할 수는 없었기에 우리는 이제 새로운 아키텍처의 도입에 대해 고민을 하게 되었고 결정을 해야했죠. 그러기 위해 먼저 목적을 다시 정의내렸습니다.
1) 유지보수
2) 자동화된 테스팅에 적합한 모델
3) 새로운 개발자라도 빠르게 적응하고 개발이 가능한 수준의 난이도와 복잡성을 가진 패턴

자, 그러면 어떤 선택을 해야 여러 가지 문제를 해결하고 이 난관을 헤쳐나갈 수 있을까요? 아이템을 찾으러 던전(Dungeon)으로 떠나봅시다.

Step 2. 앱 아키텍처에 대해 짧은 이야기

이미 많은 개발자들이 앱 개발에서 가장 효율적이고 유지보수 및 테스트에 적당한 앱 아키텍처에 대해 많은 고민을 했습니다. 우리는 그 결과물로 MVC, MVVM, MVP, VIPER 등 다양한 아키텍처를 접할 수 있었고 대부분의 iOS 개발자들이 위의 아키텍처 중에서 하나를 선택하여 개발하고 있을 것입니다.
어떤 앱 아키텍처가 좋은 아키텍처인가요? 이 주제는 이미 많이 언급되었고 많은 논란이 있었습니다. 어떤 사람은 MVVM이면 충분하다고 하고 어떤 개발자들은 VIPER와 같이 UI에 대해 조금 더 깊은 고민을 한 아키텍처가 iOS 개발에 적합하다고 이야기합니다. 물론 어떤 아키텍처를 선택하더라도 장단점은 존재합니다. No Silver Bullet; 한방에 모든 문제를 해결해줄 은탄환은 존재하지 않는 것이죠.

이러한 주제에 대해 이미 훌륭한 아티클들이 있고 다들 한 번씩 읽어보셨으리라 생각합니다. 예를 들면 Realm에서 소개한 아키텍처에 관한 글과 같은 것 말이죠. (https://academy.realm.io/kr/posts/krzysztof-zablocki-mDevCamp-ios-architecture-mvvm-mvc-viper/)

Step 3. MVVM을 기반으로 CUSTOM 하기

마이리얼트립 iOS 개발팀에서는 여러 차례 토론을 거쳐 MVVM을 앱 아키텍처로 선정하고 기존에 개발하면서 불편했던 점을 우리에게 맞도록 살을 붙여서 우리에게 적합한 아키텍처로 키워나가기로 결정했습니다. MVVM을 선택한 이유는 유지보수에 적합한 구조이면서 동시에 심플하다는 점이었습니다. MVVM은 논쟁이 불필요할 정도로 이미 검증되어 있으며 많은 iOS 개발자들에게 인기 있는 앱 아키텍처였고, VIPER보다는 심플하기 때문에 빠른 개발에 쉽다는 장점이 있습니다.
단점도 존재합니다. 라우터의 부재로 인해 if-else문이 사용될 수밖에 없고 이로 인해 MVVM의 가장 큰 장점인 테스트용이성에 영향을 준다는 것이죠. 물론 if-else로 인한 유지보수의 어려움도 한몫하지요.
이러한 MVVM의 장단점에 대해서는 여러 아티클에서 다루고 있으니 반드시 읽어보시길 바랍니다.

따라서 MVVM을 사용하면서 반드시 지켜야 할 Rule을 만들었습니다.

#1. Two-Way Binding을 위해 UI 업데이트가 필요한 곳은 반드시 RxSwift를 적용한다 : 이미 많은 프로젝트가 RxSwift를 적용하고 있습니다. 러닝 커브가 낮은 편은 아니지만, Code가 간결해지고 Data 변경에 따른 UI 업데이트를 고민할 필요가 적어집니다. 더 설명이 필요없겠죠.

#2. ViewController 하나당 ViewModel을 하나씩 생성한다 : 물론 하나의 ViewModel을 다수의 ViewController에 Injection 할 수도 있습니다. Code가 많이 중복되고 비슷한 형식의 ViewController이라면 ViewModel을 1:1로 만드는 것이 불필요하게 느껴질 수도 있습니다. 그러나 저희는 반드시 1:1로 ViewModel을 만드는 것을 원칙으로 하였습니다.
Injection이 필요한 상황에서는 ViewModel의 중복되는 부분들을 Protocol로 분리하여 새로운 ViewModel에서 처리하는 것이 규칙입니다. 이로 인해 복잡성이 낮아지고 추후 새로운 개발자가 Code를 분석하는 것에도 어려움이 적어졌습니다.

명심하십시오. 새로운 프로젝트를 시작할 때 가장 큰 러닝 커브는 <기존 소스 분석하기> 입니다.

#3. 관심사 분리하기(separation of concern) : 하나의 객체가 하나의 역할을 수행해야 하는 매우 심플하지만 명확한 디자인 패턴입니다. ViewModel에는 여러 가지를 담아낼 수 있겠지만 각각의 객체로 최대한 분리해야 합니다. 그렇지 않으면 ViewModel이 비대해지고 결국은 MVC 패턴의 ViewController처럼 모든 처리가 ViewModel에게 위임되어서 하나의 커다란 스파게티 소스가 될 것입니다.

마이리얼트립에서는 어떻게 문제들을 해결했는지 살펴보겠습니다.

어떤 부분은 MVVM에서 기본적인 내용이기 때문에 단순 반복인 내용도 있을 것입니다. 그러나 단순히 MVVM만을 적용한 것뿐 아니라 MVVM을 기본으로 하여 좀 더 유지보수가 쉽도록 여러 Class를 분리하고 역할을 나누어 주는 것에 대해 집중해서 봐주세요.

[구조(Structure)]
사용자가 빈번히 보게 되는 상품리스트를 만들어 봅시다. 상품 리스트는 일반적으로 하나의 ViewController에 UITableView를 사용해서 상∙하 로 스크롤 하면서 상품을 Display 하는 구조로 되어있습니다.
이때 기본적으로 만들어지는 Class 는 다음과 같습니다.

  • OfferlistViewController : ViewController에서는 UI 처리에 대해 관심을 가지고 있습니다. OfferlistViewModel에서 API 요청을 통해 가지고 온 데이터를 OfferlistViewModelData에서 가공하고 이를 OfferlistViewController에서 보여주고 사용자의 인터렉션을 받아 다시 ViewModel에게 해당 요청을 전달하게 됩니다.
  • OfferlistViewModel : 해당 화면에 대한 초기 데이터를 처리하고 API 요청에 대한 역할을 처리합니다.
  • OfferlistViewModelData : MVVM의 ViewModel 역할을 수행합니다. Model의 데이터를 업데이트하고 View에서 보여줄 데이터를 가공하여 View에게 전달합니다.
  • OfferlistViewController+Style : ViewController에서 사용하게 되는 여러 디자인적인 요소를 Structure로 관리합니다.
  • OfferlistViewParam : OfferlistViewController에서 알아야 하는 초기데이터를 전달하기 위해 사용되는 Structure입니다.

각각의 Class가 처리하는 역할에 대해 자세히 살펴보겠습니다.

[OfferlistViewController]
기본적인 ViewController 역할을 처리합니다. ViewModel객체와 UITableView를 포함한 다양한 디자인요소들을 관리하고 ViewModel로부터 업데이트된 Model 정보를 바탕으로 View와 ViewController의 UI 요소를 업데이트합니다.

위의 Code에서 볼 수 있듯이 UI 요소들에 대해 RxSwift에 대한 설정을 하고 ViewModel에 대한 설정과 API Request를 처리합니다. Rx에 대한 정보는 많은 아티클에서 다루고 있습니다. (https://medium.com/@daltonclaybrook/rxswift-mvvm-a-little-at-a-time-81ac17dcf285)

[OfferlistViewModel]
OfferlistViewModel에서는 초기 데이터를 OfferlistViewParam을 통해 획득하고 초기 데이터에 맞게 적합한 API Request를 하며 결과에 대한 처리를 OfferlistViewModelData로 넘기게 됩니다. OfferlistViewModelData는 OfferlistViewModel에서 처리해야 할 데이터의 가공을 단순히 Structure로 분리한 파일입니다.

MVVM에서 ViewModel이 수행하는 역할은 명확합니다. 데이터를 요청하고 가공하여 뷰에 넘겨주는 것, 그리고 뷰에서의 인터랙션을 요청받아 다시 데이터를 요청 및 처리하는 것입니다. 마이리얼트립에서는 ViewModel을 ‘데이터의 요청(OfferlistViewModel)’과 ‘가공(OfferlistViewModelData)’으로 분리함으로써 유지보수를 쉽게 하고 있습니다.

[OfferlistViewModelData]
ViewModelData 에서는 데이터를 가공하여 가지고 있습니다.
ViewModel에서 API Request를 하고 해당 결과를 ViewModelData가 넘겨받습니다. 그리고 각각의 데이터들에 대해 View에서 보일 형태로 가공을 하게 됩니다.
중요한 것은 View에 대한 정보를 가지지 않는다는 것입니다. 단순하게 데이터만을 가공하여 가지고 있어야 Dependency가 없어지고 UnitTest 역시 쉽게 처리할 수 있습니다.

[OfferlistViewController+Style]
하나의 화면에서 여러 가지 디자인 요소들이 사용됩니다.
이러한 디자인 요소들은 초기에 Storyboard나 XIB 파일에 지정되는 경우도 있지만 많은 경우 동적으로 코드에서 처리하고 있습니다. 문제는 여기서 시작됩니다. 비즈니스 로직(Business Logic)과 뷰를 처리하기 위한 기본 Code, 그리고 디자인 요소를 처리하기 위한 Code가 한 곳에 뒤섞이기 시작합니다.

처음에는 섞여 있어도 유지보수가 어렵지 않습니다. 자신이 개발한 Code이기 때문에 대부분을 이해하고 있으니까요. 그러나 시간이 흐르고 Code가 내 머리속에서 잊혀져갈 때쯤 디자이너로부터 요청이 옵니다. 로그인 화면의 title padding을 8px로 변경해주시고 색상을 변경해 주세요. 그리고 아래쪽 버튼의 사이즈도 변경해야겠네요. 이 간단한 처리를 하는데에 다시 Code를 분석해야 하고 심지어 break point를 걸어서 확인 하게 됩니다. 이미 간단한 디자인 요소 변경하나에 10~30분씩 시간을 소요하게 됩니다.

안드로이드의 Style 파일

안드로이드에서는 어떻게 하고 있을까요?
안드로이드에서는 이러한 부분들을 XML로 처리하고 있습니다. 반드시 강제하는 구조는 아니지만, 대부분의 안드로이드 개발자들은 Style이나 Animation 등의 요소들은 XML로 따로 개발하여 코드 분석 및 유지보수에 겪는 어려움이 낮아집니다.

마이리얼트립 iOS개발팀에서는 이와 같이 디자인 요소를 코드에서 모두 분리합니다. 덕분에 코드 분석과 유지보수에 많은 이점을 얻게 되었습니다. XML로 디자인 요소를 관리할 수도 있었겠지만, IDE 레벨에서 지원하지 않는 문제로 인해 오류가 있다 하더라도 Build Error가 발생하지 않아 오히려 위험한 상황이 생겨서 처리 못 하는 점은 아쉬운 점입니다.

[OfferlistViewParam]
메인화면에서 이 상품 리스트로 이동하게 되는 상황을 생각해 봅시다. 어떤 나라, 어떤 도시의 상품 리스트를 보여줘야 하는지에 대한 초기 데이터들을 메인화면으로부터 넘겨받아야 합니다. 아래와 같이 되겠죠?

하지만 메인화면에서 상품 리스트로 넘겨야 할 데이터들이 하나씩 추가되기 시작하면 데이터 관리가 복잡해지고 추후에 해당 화면을 개발해야 하는 개발자는 다른 ViewController에서 넘어오게 되는 초기 데이터인지, 해당 화면에서만 사용하는 변수인지가 명확하지 않아 혼란을 겪게 됩니다. 주석을 달아 놓는다고 해도 코드는 변경되고 주석이 업데이트 안 될 경우 더 혼란을 가중시킬 가능성이 있습니다.

따라서 MainViewController에서 OfferlistViewController로 데이터를 넘겨줄 때는 반드시 Structure를 통해 넘겨주는 것을 규칙으로 정했습니다. 이렇게 Structure로 데이터를 전달하기 때문에 Non-Optional 데이터를 빠짐없이 전달받을 수 있고 유효성 체크를 ViewParam 혹은 ViewModel에서 미리 처리하여 잘못된 오류를 미리 방지할 수 있습니다.

Step 4. 결론

이 글에서는 마이리얼트립 iOS개발자들이 사용하고 있는 앱 아키텍처와 왜 그렇게 사용하고 있는지에 대해서 간단히 설명하였습니다. 정확히 이야기하자면 앱 아키텍처로 MVVM을 사용하고 있고 유지보수를 위해 어떤 식으로 데이터를 다루는지에 대한 고민에 대한 이야기입니다. 또한 단순하고 기본적인 이야기들입니다. MVVM, MVP, VIPER 등에 대한 제 의견은 나중에 좀 더 자세하게 이야기할 기회가 있을 것으로 생각합니다.

마이리얼트립 iOS개발자들이 사용하고 있는 방식들이 완벽하다는 것은 아닙니다. 다만 저희는 많은 고민을 하였고 현재 저희 상황에 가장 알맞은 아키텍처를 사용하고 있다고 말씀드릴 수 있습니다. 물론 현재도 계속 새로운 Feature들을 개발하고 우리 앱에 적용할 때마다 더 좋게 Code를 만들어나갈 수 없을지 항상 고민하고 있죠.
중요한 핵심가치는 유지보수를 쉽게 하고 새로운 Feature를 빠르게 개발할 수 있는 구조를 지켜서 사용자에게 편의성을 제공해주는 것이고 이는 변하지 않습니다.

<좋은 사람들을 구하고 있어요! 함께 해요!>
마이리얼트립에서는 가치 있는 여행 문화를 만들어 나갈분들을 모시고 있습니다. 여러 포지션이 열려 있으니 아래 채용페이지에서 확인해주세요.
http://about.myrealtrip.com

우리는 아래와 같은 일들을 가장 큰 가치로 생각하고 있습니다.

  • 여행은 인생을 바꾼다. 우리는 고객의 인생을 바꾸는 사람이다.
  • 안되는 이유는 짧게, 되는 방법은 길게 생각한다.
  • 완벽하기보다는 일단 빠르게 시도해보는 방향으로!
  • 동료의 일도 나의 일이다. 혼자 할 수 있는 일은 없다.
  • 피드백은 빠르게, 그 실행은 더 빠르게!
  • 빠르고, 정확하게 그리고 책임감을 가진다.
  • 끝내기 위해서가 아니라 새로운 가치를 위해 일한다.

평소에 위와 같은 가치를 중요하게 생각하고 있지 않았나요?
단순히 일을 하는 것이 아니라 여행 업계를 선도하고 그 가운데에서 고객에게 더 좋은 인생의 경험을 남길 수 있는 중요한 일을 함께할 당신! 연락해 주세요. 같이 넓은 세계로 떠나봅시다! ♪

--

--