2.4 millionApp이 Playstore에 등록 되어있고, 모바일 개발자라면 안드로이드를 최소한 한번 쯤은 만들어 봤을텐데
안드로이드를 개발하는 우리는 얼마나 잘 개발하고 있는가?
어떤것이 잘 개발한 App일까?
개발자 입장에서는 퍼포먼스, 좋은 구조, 안정적인?
이번엔 좋은 구조의 앱을 위해 고민해온, 그리고 앞으로도 고민해나갈 저의 경험에 대해서 짧게나마 공유하고자 합니다.
이전에 진행했던 프로젝트를 소개하며 본격적으로 시작해보겠습니다.
다양한 캔버스 위에 드로잉을 하고 공유할 수 있는 앱.
주요 페이지는 3-4페이지 가량, Drawing 모듈, 네트워크 모듈 포함
개발인원 4명에 런칭까지 16개월
사진을 꾸미고 세상 누군가와 랜덤으로 교환하고 채팅을 할 수 있는 엔터테이먼트 앱.
주요 페이지는 9-10페이지 가량, Drawing 모듈, 네트워크 모듈, Database모듈
개발 인원은 3명에 런칭까지 12개월
규모 면에서 차이가 많이 나는데 오히려 개발 기간이 얼마 걸리지 않은 이유는?
개발을 진행하면서 수많은 기획 변경과 팀원 교체, 다른 앱들의 서포트를 위한 컨텍스트 스위칭 코스트 등 여러가지가 작용했겠지만,
이런 것들에 더 잘 대처할 수 있었던 것은 Swish때는 Architecture 패턴을 도입했기 때문이라 생각함.
실제로 협업을 할 때 각자 작업한 소스 코드의 Merge가 엄청 빈번하게 일어났고, 커플링이 심한 파일에서 Conflict가 많이 발생하며 불필요한 코스트가 많이 발생했다.
레고 블록으로 왼쪽 사진처럼 피카츄를 만드려고 하면 그냥 만들고 피카츄라 우기면 되지만,
오른쪽 처럼 우주 왕복선 발사대를 만드려면 그냥 만들 수 있을까? 이 경우 복잡도는 기하급수적으로 증가하여
인간이 직관적으로 이해할 수 있는 크기를 넘겨버리게 됩니다.
만약 우주 왕복선 발사대를 그냥 만든다는 것은 불가능에 가깝지 않을까 생각합니다.
우리 프로그래밍의 세계도 마찬가지 입니다. 그래서 아키택쳐가 필요한 것이죠.
프레임워크에 독립적이다.
아키텍처는 특정 라이브라리의 존재에 의존해서는 안된다. 이는 당신의 시스템을 프레임워크의 제약들로 가득채우는 대신 프레임워크를 도구처럼 사용할 수 있게 해준다.*
테스트가 용이하다. 비즈니스 로직은 UI, DB, Web Server 또는 다른 외부족인 요소 없이도 테스트 가능해야한다.
그 외에도 재사용이 용이해야하고 확장성이 좋아야한다.
결국 중요한 것은 Independent다. 컴포넌트간의 디커플링이 이루어질 수록 테스트도 용이해지고, 재사용성도 확장성도 용이해진다.
Activity 내에 이벤트를 핸들링하는 처리나 뷰에 접근하는 코드들이 모두 있습니다.
이러한 코드들의 모습은 서버 기반 동작시엔 하나의 Activity 내에 네트워크 처리를 위한
쓰레드 처리까지 하게되는 등 코드가 커지면 커질수록 가독성도 떨어지며 유지보수가 힘들어지는 코드로 가기 쉬워집니다.
안드로이드 앱도 역시 아키텍처에서 고민을 시작하던 시기에, 웹 서비스를 개발할 때 많이 사용하던 MVC패턴이 도입되기 시작했습니다.
MVC패턴의 목적은 View와 Model의 분리입니다.
쉽게 말해 보여주는 코드와 데이터 처리 로직 코드를 분리하여 중복 코딩되는 것을 막고 로직과 엔티티(데이터)를 재 사용할 수 있게 하기 위함입니다.
입력이 들어오면 Controller는 입력에 해당하는 Model를 업데이트하고
Model을 표현해 줄 View를 선택한다.
하나의 Controller는 여러개의 View를 가질 수 있으며
Controller는 여러개의 View를 선택하여 Model을 표현할 수 있다.
반대로 View는 Controller에서 어떤 동작이 일어나는지 알 수 없다.
이 때 Controller는 View를 선택만 할 뿐 View를 직접 업데이트 하지 않기 때문에,
주로 다양한 방법을 사용해 View를 업데이트 하는데 주로 Observer패턴을 통해 Model에서 View에게 Notify해 주는 방법을 사용
View는 화면을 업데이트하기 위해 Model을 직/간접적으로 참조하기 때문에 서로간의 의존성을 완전히 없앨 수 없다.
안드로이드의 경우 일반적으로 Activity(혹은 Fragment)가Controller와 View의 역할을 모두 수행 하기 때문에,
View 나 Controller 를 한쪽으로 빼게 될 경우 View 에 대한 바인딩이나 처리에서 중복 코드나 일관성을 잃어버리는 코드를 작성할 수 있다.
따라서 기본적인 MVC모델에서 세 개 역할을 완전히 분리하기가 어렵다
MVC에서 파생된 아키텍쳐 패턴.
MVC에서 Controller를 Presenter로 변경하여
프로그램을 구성하는 요소들을 Model, View, Presenter 세 가지 로 나누었다.
MVC에서 발생한 Model과 View간의 의존도를 없애기 위해 등장
Model
데이터를 가진 객체.
View
사용자(Client)에게 제공되는 UI Layer
Client에게서 입력을 받고 그 입력에 따른 이벤트를 Presenter에게 전달 한다.
Presenter
사용자 이벤트에 대한 로직을 처리하고 Model을 관리한다.
Model의 상태 변화를 View에게 알리는 역할도 수행한다.
기본적으로 View와 Presenter는 1대 1 관계이며,
일반적으로 Presenter는 Model보다 View와 더 닮은 구조로 디자인된다.
View 에 대한 직접적인 접근이 요구되는 Android 의 Activity 는 직접적인 view 접근은 Activity 가 하도록 하고 이에 대한 제어는 Presenter 가 하도록 하고 있다.
그래서 Activity를 하나의 View로서 사용한다.
이런 구조를 취함으로써 Model과 View의 의존성이 완전히 사라졌지만
Presenter와 View 간의 1:1관계로 인해 둘의 의존성이 매우 강해진다는 이슈가 있다.
하지만 개인적으로 MVC보다 더 좋은다고 판단하는 이유는 View-Model간의 의존성보다는
Presenter가 Mapper 역할을 하기 때문에 View나 Model이 변경되더라도 한쪽이 변경될 가능성이 줄어든다.
마찬가지로 MVC 모델에서 파생
Model과 View 사이의 의존성 뿐 만 아니라
View와 Controller간의 의존성도 고려하여
각 Layer가 완전히 독립적으로 작성되고 테스트 될 수 있도록 설계된 아키텍쳐 패턴
Model
데이터를 가진 객체.
View
사용자(Client)에게 제공되는 UI Layer.
Client에게 입력을 받아 View Model에게 전달한다.
View Model
View의 표현을 담당한다.
MVP와 매우 비슷하지만,
MVP에서 Presenter가 View와 의존 관계에 있었다면
ViewModel은 DataBinding을 통해 View와 독립적으로 이루어져 있다는 것이 큰 차이점 이다.
View와 ViewModel간의 상호작용에는
Command 패턴이나 Data Binding(2-way binding, Binding propagation)이 주로 사용되는데,
이로 인해 View와 ViewModel의 의존성을 완벽히 분리할 수 있다.
2015년 Android M에서 Data Binding Library를 소개한다. 이 Library는 번거로웠던 MVVM 아키택처 구현이 큰 힘들 싣어주었다.
이 라이브러리 이후로 MVVM에 대해서도 많이 긍정적이다.
또다른 Activity
MVP와 MVVM의 기본적인 개념에 대해서 알아봤는데, 이것만으로는 아쉬우니까 지금 제가 프로젝트 내에 어떤 아키택처 패턴을 사용하고 있는지에 대해서 공유드리면서 마무리를 지으려 합니다.
제가 좋아하는 밥 아저씨가 2012년에 작성한 글인데 아직도 인기가 많은 Clean Architecture. 최근 안드로이드 진영에서 이런 CleanArchitecture와 MVP, MVVM을 병행해서 사용하는 것을 보고 저도 한번 따라해봤습니다.
목적은 INdependent
The Dependency Rule
이 개념적인 원들은 소프트웨어의 각 영역을 나타낸다. 일반적으로 멀리 있는 원으로 갈 수록, 소프트웨어의 높은 레벨을 뜻한다. 바깥 원은 메커니즘이고, 안쪽 원은 정책을 의미한다.
이 아키텍쳐를 동작하게 만드는 가장 중요한 법칙은 The Dependency Rule이다. 이 법칙은 모든 소스코드 의존성은 안쪽으로만 향할 수 있다는 것인데, 안쪽 원에 있는 어떤 것도 바깥쪽 원에 있는 것을 알아서는 안 된다. 메서드, 클래스, 변수 등 바깥쪽 원에 있는 어떤 것도 안쪽 원에서 언급될 수 없다.
Dependency Inversion Principle
가장 기본적으로 Network API를 연동하여 Data처리를 하거나 대량의 Local Data 처리를 할 때 UI 프리징을 방지하기 위해, 쓰레드를 사용하는데 주로 사용하는 것이 AsyncTask. 복잡한 로직으로 코드가 어쩔 수 없이 지저분해지고 코드 중복이 발생한다.
이를 해결하기 위해 보다 수월하고 응용도 높게 비동기 기반의 데이터 처리를 가능하도록 도와주는 RXJava, RXAndroid를 알아두시면 좋을 것 같습니다.
결론부터 말씀드리자면 어떠한 아키택처든 모든 상황을 한방에 해결해줄 수 있는 실버불렛은 없다는 것입니다.
제가 경험한 바로 말씀드리자면 규모가 작고 생명주기가 짧은 프로젝트는 MVVM으로 빠르게, 규모가 크고 상대적으로 견고해야되는 프로젝트는 MVP와 CleanArchitecture를 혼합한 형태로 진행하는 것이 제일 좋을 것 같네요.