2020. 1. 13. 21:14
Android Architecture Part1: 관심사의 분리
새로운 안드로이드 프로젝트를 진행하게 되면, 우리는 안드로이드 SDK, Convention 등을 고려하면 모바일 애플리케이션을 개발한다. 하지만, 안드로이드 개발 방식은 해를 거급하여 발전해 왔고, 수년전과 비교하면 많은 것들이 달라졌다.
대고객 서비스를 제공하는 플랫폼을 제공하는 업무를 진행하면서 우리는 고객 최상의 경험을 선사하기 위해 다양한 기능을 개발하고, 애플리케이션의 성능을 최적화하며 UX와 프로세스를 더 직관적인 형태로 개선하기 위해 많은 시간을 보내고 있다. 하지만 이런 과정속에서 일관성 있는 코드를 유지하는 일은 쉬운 일이 아니다.
수십만이 넘는 코드라인 속에서 어떻게 모바일 엔지니어들의 업무 효율을 높이고 앱의 신뢰성과 생산성을 개선할 것인가?
이 질문을 다시 설명하자면, 수십만이 되는 코드를 어떻게 하면 구조화 할 것인가? 로 표현할 수 있을 것이다.
관심사의 분리(Separation of concerns)
갓 클래스의 등장
프로젝트 초기에는 모바일 엔지니어들이 많지 않았고, 요구사항 역시 복잡하지 않았다. 하지만, 지속적인 비즈니스 성장과 함께 추가기능의 개발, 더 많은 모바일 엔지어니들과 함께 협업을 해야했다. 이로 인해 기존 컴포넌트에 더 많은 기능이 추가되었고, 이는 악명 높은 안티 패턴인 갓(God) 클래스/오브젝트/모듈(많은 일을 하고 매우 복잡해서 아무도 건드릴 엄두가 나지 않는 클래스)를 경험하게 되었다.
안드로이드 초창기에는 이러한 갓 액티비티(God Activity)를 가지고 있는 앱들이 많았다. UI를 비롯해 비즈니스 로직, 네트워크 통신 등 모든 것을 액티비티 내에서 모든 것을 처리하는 거대한 클래스가 되었다. (다른 이유가 있겠지만) 구글에서는 액티비티의 UI를 구성하는 기본 요소였다. 하지만, 액티비티는 결국 UI, 비즈니스 로직, 네트워크 통신 등 뭐든 것을 처리하는 거대한 클래스로 변해 버렸다.
관심사의 분리는 안드로이드 애플리케이션에 국한된 이야기가 아니다. 웹 애플리케이션에서도 이런 부분을 해소하기 위해서 MVC 혹은 진화된 MVC 패턴을 적용하고 있을 것이다.
안드로이드 아키텍처
대고객 서비스앱의 사용자가 1000만 명을 넘어 지속적으로 증가하고 있고, 아주 빠른 속도로 성장해 나가고 있다. 그리고 고객의 편의성을 위한 기능을 비롯해, 다양한 데이터 분석을 위한 기능등 복잡성은 하루가 다르게 증가하고 있다. 그 뿐만 아니라 최신 안드로이드 트렌드에 맞추어 나가기에는 많은 것들이 부족했다.
새로운 아키텍처를 결정할 때 무엇을 고려해야하는가?
- 전반적인 코드 품질 향상
- 많은 개발자들이 함께 개발할 수 있도록 유지보수의 편의성
- 어떤 개발자들이 개발을 해도 쉽게 눈에 들어오는 코드의 가독성
- 유닛테스트를 통한 신뢰성 및 안정성 확보
- 개발 커뮤니티에서 널리 사용되는 패턴(입증되고 신뢰할 수 있는 표준)
위 기준에 따라, MVC, MVP, MVVM 아키텍처의 적합성을 알아보기로 했다.
1. MVC 패턴 - MODEL, VIEW, CONTROLLER
MVC 패턴은 MODEL, VIEW, CONTROLLER로 구성된다.
MODEL은 앱의 데이터 및 비즈니스 기능을 나타낸다.
VIEW는 MODEL의 시각적으로 표현한 것으로 스크린과 위젯으로 구성된다.
CONTROLLER는 사용자 입력에 반응하는 컴포넌트로, 유저와 앱을 잇는 다리 역할을 한다.
안드로이드에서는 액티비티/프래그먼트가 VIEW와 CONTROLLER 역할을 동시에 하는 경우가 많기 때문에 MVC를 구현하기가 쉽지 않다. 보통 모놀리식(Monolithic) 구조로 만들어지며, 모던 안드로이드 앱에서는 별로 사용되지 않는다.
2. MVP 패턴 - MODEL, VIEW, PRESENTER
MVP 패턴은 MODEL, VIEW, PRESENTER로 구성된다.
VIEW와 MODEL은 더 느슨하게 결합되어(Interface), 관심사(Concern)를 명확히 구분해 준다.
VIEW는 CONTROLLER 역할도 하며, 사용자 입력은 PRESENTER에 위임한다.
PRESENTER와 VIEW는 1:1 관계를 갖는다.
MVP는 테스트, 유지보수, 확장성에 용이한 패턴이다. 추가 라이브러리 또는 프레임워크를 필요로 하지 않으면서, 기존 코드베이스에서 쉽게 통합할 수 있다. MVP는 학습하기 비교적 용이하며, 특히 MVC 패턴을 사용해 본 경험이 있다면 쉽게 적응할 수 있다.
그러나 작은 기능의 경우 MVP 패턴을 사용하면 복잡성이 증가할 수 있으며, 평범한 MVC 패턴으로 구현하는 편이 더 간단한 경우도 있다. 또한 MVP는 컴포넌트간의 의사소통을 위한 콜백을 남발할 여지도 가지고 있다. (소위, '콜백 지옥') 이를 개선하여 콜백 남발을 방지하는 방법 중 하나로 RxJava와 같은 반응형(Reactive) 프로그래밍이 있다.
3. MVVM 패턴 - MODEL, VIEW, VIEW-MODEL
MVVM 패턴은 MODEL, VIEW, VIEW-MODEL로 구성된다. 이 패턴은 UI와 애플리케이션 로직이 분명히 구분된다.
MODEL은 비즈니스 규칙, 데이터 접근, 모델 클래스를 처리한다.
VIEW는 사용자의 입력을 VIEW-MODEL에 위임한다.
VIEW-MODEL은 VIEW와 MODEL 사이의 중개자 역할을 한다.
MVVM 패턴은 MVP와 비슷하게 테스트 유지보수, 확장성이 뛰어나며, 데이터 바인딩 라이브러리 또는 프레임워크가 필요하다. (MVVM을 고려했을 당시, Google의 DataBinding Library는 베타 버전이었고, 아직 안드로이드 커뮤니티에 널리 사용되지 않았다.)
단점으로는 데이터 바인팅(XML 파일) 디버깅이 어렵다는 점, 그리고 MVC/MVP 패턴에 비해 Learning Curve가 다소 있다는 점이 있다.
MVVM과 MVP 둘 다 매우 훌륭한 아키텍처이다. 두 패턴 모두 각각 다른 장점과 단점을 가지고 있지만, 완전한 "관심사의 분리"를 가능케 하고 유닛테스트에 용이하다는 공통점을 가지고 있다. 현재 트랜드는 MVVM으로 많이 가고 있지만, 개인적으로는 두 패턴 모든 좋은 선택지라고 생각한다. 그리고 프로젝트의 특성과 협업하는 동료들을 비롯해 여러 환경에 따라 적합한 아키텍처를 선택하는 것이 좋을 것 같다.
리팩토링 전략
전체 코드베이스에 새로운 아키텍처를 적용하는 것은 쉽지 않은 작업이며, 대고객 서비스에서는 더더욱 서비스를 진행하면 이를 적용하기 위해서는 여러 가지 준비가 필요하다.
1. 단단한 기반에서 시작하기
우리는 잘 다져진 기반 위에서 코드 품질 개선과 함께 리팩토링을 진행해야 한다. 이를 달성하기 위해 SonarQube의 많은 기능을 사용해야한다. SonarQube는 중복 코드, 코딩 표준, 유닛 테스트, 코드 커버리지, 코드 복잡도, 주석, 버그, 보안 취약점에 대한 보고서를 제공하는 솔루션이다.
SonarQube의 리포트를 CI 시스템에 통합하여, 매번 빌드 시마다 새 리포트를 생성하도록 한다. Rule의 기반은 안드로이드 Lint를 따르도록 하되, 원하는 적정 수준에 부합하도록 설정한다. 유닛 테스트의 결과 또한 SonarQube의 리포트에 통합한다. 그 뿐만 아니라, Git blame 및 엄격한 정책(Critical, Blocker, Major 경고가 없도록 함)을 활용하여, 엔지니어들에게 SonarQube의 경고/이슈를 각 담당자에게 할당하여 지속적으로 해결하도록 한다. 이러한 방식으로 모든 엔지니어들이 참여 코드 품질 개선의 부담을 효율적으로 분담한다.
SonarQube는 발생 가능성이 있는 경고/이슈 수정 뿐만 아니라, 중복 코드나 더 이상 사용하지 않는 코드를 대폭 줄이고 코드 커버리지를 향상시키는데 활용할 수 있다.
이를 성공적으로 마무리하면 애플리케이션의 전반적인 복잡도는 크게 감소하게 된다.
2. 커스텀 라이브러리
단단한 기반을 바탕으로 앱 전반적으로 지속적인 MVP/MVVM 패턴을 달성하기 위한 도구들이 필요하다. 일관성을 지켜나갈 수 있도록 도움이 되며, 불필요한 보일러 플레이트(Boiler plate) 코드를 줄이는데 큰 효과를 얻을 수 있다. 만약 View와 Presenter의 바인딩을 라이브러리 내부에서 처리할 수 있다면, View 안에서는 단지 Presenter를 생성하는 메소드만 Override 하면 될 것이다.
MVP/MVVM 패턴을 적용하는 데에 필요한 커스텀 라이브러리들은 아래와 같다.
3. 엔지니어들과의 Alignment / 교육
아키텍처 변경은 몇 명의 시니어 엔지니어만으로 진행할 수 없다. 프로젝트에 관여된 모든 개발자들과 같은 공감대에서 우리가 얻을 수 있는 장점들을 설명하고, 예상되는 Pain Point들을 확인하면서 지속적인 대화가 필요하다. 그 다음 단계로 우리는 어떠한 패턴으로 어떻게 리펙토링을 진행해야 하지는 샘플 코드와 지속적인 교육이 필요하다.
또한, 모든 것들이 클리어할 때 엔지니어들은 혼선을 최소화하여 진행할 수 있다. 그래서 잘 정리된 코드 가이드 라인을 만들어 지침으로 삼아 이를 공유하고 가이드할 수 있다. 아래의 가이드 라인은 안드로이드 컨벤션으로 유용하게 활용할 수 있다.
4. 작업 부담
지속적인 엔지니어들과의 소통과 교육을 통해서 어느 정도 공감대와 방향성이 일치했다면, 이제 남은 작업은 리팩토링을 진행하는 것이다. 단순히, MVP/MVVM 패턴으로 적용하는 가이드보다는 Domain Owner를 정리하고 관리 가능한 Scope를 정의하여 이를 지속적으로 모니터링할 수 있도록 한다. 예를 들어, 엔지니어별로 하나의 도메인을 정하고 리펙토링의 기준을 MVP/MVVM 패턴 적용과 함께 테스트 케이스를 작성하여 목표 80%이상을 유지하도록 하여, 뚜렷한 목표를 제시하고 이를 달성할 수 있도록 독려하는 것이다.
큰 프로젝트에서 하루 이틀만에 아키텍처를 변경하는 것은 불가능하다. 게다가 대고객 서비스 관점에서는 많은 기능과 편의를 제공하면서 진행하면서 이를 진행하기에는 쉬운 일이 아니다. 그렇기 때문에 앞에서의 의사결정과 점진적으로 적용할 할 수 있는 전략을 세우고 때로는 긴급히 진행해야하는 기능 개발로 인해 연기되기도 한다. 하지만, 이러한 노력들이 유닛테스트 커버리지 80%를 달성하고, 코드 품질을 크게 향상시키면서 동시에 각종 경고와 중복코드 및 사용하지 않는 코드를 제거함으로서 앱의 안정성과 생산성을 높이는 데 많은 기여를 하고 많은 엔지니어들의 성장에도 도움이 된다는 것을 느끼게 한다.