실험과 현지화에 흔들리지 않는 모바일 엔지니어링
안녕하세요. 저는 당근마켓에서 글로벌 프로덕트를 개발하고 있는 iOS Engineer David이에요.
이번 글에서는 다양한 국가에서 현지화를 하며 시장을 뚫고 있는 Karrot Global Product Team의 iOS 엔지니어들과 그리고 유저에게 더 나은 가치를 전달하기 위해 다양한 실험을 빠르게 진행하고 있는 당근마켓 프로덕트팀의 iOS 엔지니어들이 어떻게 함께 협업을 하며 실험과 현지화에 흔들리지 않는 모바일 엔지니어링을 하고 있는지 공유해보고자해요.
서비스의 성장에 따른 객체의 단일 책임 위반과 복잡도의 증가
클린 아키텍쳐(Clean Architecture)는 보편적으로 알고 있는 문장을 인용하자면 다음과 같아요.
좋은 소프트웨어는 유연한 소프트웨어고 이러한 유연한 소프트웨어란 고객의 요구사항에 따라, 쉽게 그 기능을 변경하거나 추가할 수 있는 제품을 말하고 엔지니어가가 제품의 기능을 쉽게 변화할 수 있는 구조를 설계하도록 돕는 아키텍처에요.
다양한 디자인 패턴을 채택과 설계한 여러 객체들간 협력을 통해서 비즈니스 목표를 달성하고 있지요.
Mobile Engineering 생태계에서 보면 사용자에게 가치를 전달하기 위해서 UI/UX뿐만 아니라 비즈니스를 수행하기위해 다양한 모바일 패턴들이 있죠 대표적으로 MVI(Model-View-Intent), MVVM(Model-View-ViewModel), VIPER, VIP 등등이요.
작은 규모의 서비스에서는 앞서 말한 어느 디자인 패턴을 채택하든 비즈니스를 잘 수행하지만 규모가 커짐과 동시에 다양한 실험 그리고 현지화 과정에서 Intent/ViewModel/Interactor/Presenter와 같은 객체들의 책임이 많이지게 되고 여러 컨디션의 증가에 따라 복잡도의 상승에 따라서 여러 부작용을 낳게 되지요.
따라서, 실험과 현지화에 흔들리지 않는 모바일 엔지니어링을 잘하기 위해선 이러한 단일 책임을 위반하는 객체들의 문제점을 해결해야하는데 이런 해결과정에 대한 이야기를 자세히 다뤄보고자해요.
당근마켓 iOS 프로덕트가 CleanSwift(VIP)를 채택하기 시작했던것은 19년도 7월 쯤이였어요. 그 전에는 MVVM패턴을 채택했었고 MVVM에서 ViewModel에서 In/Out에서 RxSwift를 의존하다 보니 시간과 이벤트에 대한 테스트를 많이 신경썼었는데 Massive해지는 ViewModel 해결과 더 나은 테스트작성 경험을 물색하던 과정에서 위와 같은 패턴을 채택하기 시작했어요.
당시에는 한국에서만 서비스 하고 있었고 그로쓰 단계에서 새롭게 기능들을 많이 만들고 있던 과정이여서 큰 불편함을 느끼지 못했어요.
하지만 이후 본격적으로 영국/일본/캐나다 진출과 그리고 한국에서의 서비스 성장이후 수 많은 동료들이 합류하게 되고 다양한 실험을 하는 과정에서 CleanSwift(VIP)에서 Interactor와 Presenter 또한 Massive해지고 내부로직들의 복잡도 상승에 따른 부작용이 나타나기 시작했어요.
VIP를 교과서적으로 사용하기에는 Scalable하지 않다는 현실을 마주하게 되었고 이러한 부채들은 새로합류하게 된 iOS 엔지니어들에게 Pain-Point로 다가오게 되었고 국가간의 사이드 이펙트와 실험에 의한 사이드 이펙트의 리스크를 안고가야하는 현실에 속상했던 경험도 생각이 나네요. VIP가 나쁘다는 의미는 아니에요. 다른 패턴을 채택했더라도 이런 고충은 생길 수 밖에 없었을꺼에요.
하지만 인간은 항상 문제를 맞닥드리면 해결해나가듯이 합류해주신 뛰어난 iOS 엔지니어들의 경험과 능력들을 잘 버무리면서 Interactor 내에서의 비즈니스 로직들을 단일책임을 가지는 UseCase라는 객체로 분리하기 시작하고, 국가나 실험 상태에 따라서 Builder라는 객체에서 의존성 주입을 해나가는 등과 같은 개선을 거쳤왔어요.
여기까지는 Interactor를 어떻게 Scalable하게 설계해나갈지 이야기였고 이런 변화를 이끌어준 뛰어나고 다양한 경험을 가진 동료들에게 우선 감사의 말씀을 드려요.
여기까지가 사용자 눈에 직접적으로 보이지 않는 비즈니스 로직에 해당하는 도메인 영역이고 담으로는 현실적으로 눈에 보이는 Presentation 영역에 대해서 어떻게 실험과 현지화에 흔들리지 않는 설계를 하고 있는지 이야기해보고자 해요.
Factory 패턴을 활용한 View의 Data 또는 Model제공
이 글을 읽기전에 앞서서 우선 독자와 저와 몇가지 공감대를 형성해보고자해요.
- View는 험블객체(Humble Object)에요. 험블(Humble)은 사전적으로 “보잘 것 없는"을 의미하며 험블객체 패턴은 이러한 테스트 하기 쉬운 것과 어려운 것을 분리하는 패턴을 의미하며 이렇게 분리된 것이 테스트하기 쉬운 Presenter와 어려운 View로 나눌 수 있다고 볼 수 있어요.
- View는 가공된 보여줄 정보들을 User Interface에 대입하지만 이러한 정보를 직접처리하지는 않아요. (이런건 비즈니스 로직에서 수행해요)
- View가 사용할 데이터(ViewModel/ViewData…)을 가공 및 생성하는 객체는 테스트가능해야해요.
공감되셨나요?
자 그럼 다시 본론으로 들어와서 CleanSwift의 Presenter를 같이 보도록하죠.
Presenter에는 비즈니스 행위의 결과에 맞춰서 View에 필요한 데이터를 가공하게 되는데 과거에는 각 데이터를 가공하는 로직 내에서 국가 분기가 일어나기도하고 실험에 따라 문구를 다르게 표현하기도하고 어떤 View를 rendering해줄지 결정하기도 했어요.
- 일본과 한국에는 당근이가 있지만, 캐나다와 같은 영어권 국가에서는 당근이를 보여주지 않아요
- 대조군에서는 A문구를 보여주고 실험군에서는 B문구를 보여줘요
- 영국에서는 피드상에서 중고거래 게시글의 거리를 표시하고, 다른 나머지 국가에서는 거리대신 시간을 표시해요.
- 영국, 캐나다에서는 H3 System을 활용하여 동네범위를 보여주고, 나머지 국가에서는 정의된 Polygon을 기반을 동네 범위를 보여줘요.
이러한 분기로직들이 많아짐에 따라서 자연스럽게 복잡도가 향상되게 되고 유닛테스트를 작성하는 과정에서도 고려해야할 매개변수의 증가에 따라서 생산성이 저조해질 수 밖에 없어요.
이러한 문제를 해결하기 위한 다양한 방법들이 있는데 몇가지 주변으로 부터 들은 전략들을 우선 소개해보고 이후 Karrot에서 해결한 방향에 대해서 소개해보고자 해요.
전략1. Copy & Paste
Marty의 이야기에 따르면 극단적인 케이스중 하나인데 어떤 게임회사에서는 게임 현지화를 같은 화면이지만 국가별로 copy&paste했다고 해요. 그래서 제가 물었죠. “그럼 20개국 진출하면 20개의 화면 복제가 일어난건가요?!” Marty가 말씀하시길 그렇다고 하셨어요.
물론 이런 전략도 나름 방법이긴해요 각 국가팀마다 R&R을 잘나누기도 쉽고 국가간에 사이드이펙트는 덜하겠죠. 하지만 결국 증가하는 보일러플레이트의 관리부담은 고스란히 엔지니어의 몫이 될 수 밖에 없다는 부분이 단점이라 보아요.
전략2. ViewData/ViewModel Factory 추상화 및 국가별 구현체
위 방법은 소수의 국가에 진출했을 때 나름 나쁘지 않은 시도라 생각해요. 국가침투 과정에 좀 더 집중할 수 있고 국가간에 사이드 이펙트도 줄일 수 있구요. 물론 국내에서 수 많은 실험을 하기 전까지는 말이죠.
특히, 여기서 자세히 보면 유저라는 공통적인 도메인을 공유하고 있지만 더 자세히 우측에 보면 점수 표기 방법이 다른 걸 볼 수 있는데요.
한국, 일본과 같은 상대적으로 문해력이 높은 아시아권 문화에서는 “매너온도"라는 개념을 채택하고 있지만 반대로 영미권 문화에서는 “Karrot Score”라는 개념을 채택하고 있어요. 결국 단순 국가별 구현체를 생성하더라도 “매너온도", “Karrot Score”에 대한 보일러 플레이트가 발생하게 되고 각 문화권 국가 확장하는데 있어서 전략1번에 겪은 단점을 겪을 수 밖에 없기도 해요.
매너온도와 Karrot Score에 대해서 자세한이야기는 당근마켓 ‘매너온도’ 글로벌 수출기를 함께봐도 좋아요.
그래서 iOS 엔지니어는 다음과 나올전략을 채택하게 되어요.
전략3. ViewData/ViewModel Factory 추상화 및 도메인 및 기본값 그리고 국가별 구현체
전략2와 큰차이점은 국가단위로 나누는 사고방식이 아니라 도메인 관점으로 우선 나눈 후 나눠진 범위 내에서 필요에 따라 기본 구현체 설계와 필요에 따른 국가별 구현체를 설계하는 전략이에요.
이러한 설계방향을 가져가게 되었던 계기가 있었는데요. 과거 과도기 시절 국가단위로 R&R을 나눠서 엔지니어링을 했던적이 있었어요.
프로필이라는 도메인에서 한국은 이웃프로필이라는 도메인을 만들어서 개발하고, 글로벌에서는 글로벌 프로필 도메인 내에서 일본은 매너온도, 영국/북미와 같은 영어권은 Karrot Score를 채택했었죠. 이러한 채택을 할 수 밖에 없었던 이유는 해외 프로덕트를 만드는 사일로팀과 국내 프로덕트를 사일로팀이 나눠져 있음에 따라서 발생할 수 밖에 없었던 엔지니어링 방향이였어요.
프로덕트가 해외에서 성공하기 위해서는 국가별로 시도한 경험들을 잘 베이킹해서 다른 국가에도 적용하며 실험해 나가면서 궁극적으로 전 세계 유저들에게 좋은 경험과 가치를 가져다 주는게 성공의 핵심이기 때문에 전략2에서 설명한 국가별로 초점을 맞춘 구현체 설계가 아닌 선 도메인 후 필요에 따른 국가별 구현체 설계를 채택하게 됐었어요.
또한 기본 구현체가 있으면 필요에 따른 국가별 구현체 뿐만 아니라 실험에 대한 구현체도 자연스럽게 설계할 수 있게 된 것이죠. 이러한 구현체들에 대한 의존성 주입은 아래 사진과 같이 Builder에서 이뤄지고 있구요.
마무리
불과 3년 전만 해도 모바일 개발하는데 있어서 MVI(Model-View-Intent), MVVM(Model-View-ViewModel), VIPER, VIP 등과 같은 모바일 패턴들에 대한 관심이 많았었고 서로간에 전쟁?과 같은 열띤 토론을 하기도 했었죠. 하지만 단순 모바일 설계 패턴을 벗어나 실험과 현지화에 흔들리지 않는 설계에 대한 사고방식의 변화를 이끌 수 있었던건 크게 두가지를 자신있게 말할 수 있을꺼 같아요.
함께하는 좋은 동료들과 그들의 이전 경험들에 대한 존경 그리고 단순 소프트웨어 엔지니어링이 아닌 고객중심의 프로덕트 엔지니어로서 마음가짐이 있었기에 이러한 변화를 이끌 수 있었어요.
우리 모두가 제품을 만드는 과정에서 “프로덕트 디자이너는 최대한 일관성을 맞추고 싶어하고, 프로덕트 메니저들은 상황에 따른 요구사항을 달성하고 싶어하고, 프로덕트 엔지니어들은 위의 두 상황에 대해서 흔들리지 않는 견고한 엔지니어링을 할 수 있어야한다”는 이야기를 마지막으로 글을 마무리하고자해요.
긴 글 읽어주셔서 감사해요.
위에서 고객은 아키텍쳐를 사용하게 될 동료들과 제품을 사용하게 될 유저들을 의미해요.
당근마켓(Karrot)의 iOS 엔지니어들은 외부경험에 대한 자극에 대해서 아직도 목말라하고 있어요. 혹시 이 글을 보고계신 iOS/Android 엔지니어분들이 계시다면 함께하시지 않으시겠어요?
그리고 이런 글을 작성할 수 있게 도움을 주신 Jaxtyn, Ryan, Elon.park, Lychee, Romie, Corbin, Ray에게 감사의 말씀을 드려요.