쿠팡 안드로이드 아키텍처 — Part 3
리패키징을 통한 의존성 제거
By Seongchul Park (박성철)
시리즈
이번 포스트는 “쿠팡 안드로이드 아키텍처” 시리즈의 세번째 이야기입니다.
- Part 1 — 관심사의 분리
- Part 2 — 앱의 모듈화
- Part 3 — 리패키징을 통한 의존성 제거
본 포스트는 영문으로도 제공됩니다.
초창기 모바일 애플리케이션 가운데 상당수는 패키징 전략에 시간을 많이 투자하지 않았습니다. 패키지 하나에 많은 클래스들을 넣고 개발을 진행하고 애플리케이션이 점차 성장해가면 액티비티, 프래그먼트와 같은 컴포넌트 단위로 패키지를 생성하고 클래스들을 이동하는 경우가 많았습니다. 이런 방식으로 개발하다 보면 또 다시 하나의 패키지에 수많은 클래스들이 생겨나게 되고 이는 애플리케이션 유지보수에 커다란 걸림돌이 되기도 합니다.
저희는 앱 모듈화 과정에서 코드베이스를 13개의 코어 모듈(core module)로 성공적으로 분리했지만, 하나의 큰 덩어리로 남아있는 피처 모듈(feature module) 역시 컴포넌트 단위로 분리되어 있는 패키지들을 많이 포함하고 있었습니다. 이로 인해 패키지 간 의존성이 너무나 복잡한 상태였고, 이를 해결하기 위해 리패키징(Repackaging) 작업을 진행하기로 결정했습니다.
컴포넌트 패키징 vs. 피처 패키징
작업을 진행하기 위해 두가지 패키징 구조를 비교하기로 했습니다. 컴포넌트 패키징은 액티비티, 프래그먼트, 어댑터, 핸들러와 같은 컴포넌트를 바탕으로 패키징을 하고, 피처 패키징은 피처 별로 패키징을 하며 단일 피처에 액티비티, 프래그먼트, 또는 어댑터가 포함되는 방식입니다. 저희는 아래와 같은 이유로 피처 패키징을 선택하게 되었습니다.
확장성: 피처 패키징의 명백한 장점은 확장성입니다. 새로운 피처가 생길 때마다 손쉽게 새로운 패키지를 생성하거나 시간이 지나 기능이 거대해졌을 때 작은 기능으로 쉽게 재구성할 수 있습니다. 만약 액티비티들을 모아놓은 컴포넌트 패키지에 클래스들이 100개가 넘게 있다고 가정해보면 개발자들은 원하는 클래스를 찾기 위해 많은 시간을 소비해야 하고, 만약 클래스명을 잊어버린다면 더 많은 시간을 소비할 수 밖에 없습니다.
제거 용의성 & 가독성: 개발자는 많은 시간을 통해 새로운 피처를 개발합니다. 하지만 새로운 피처가 매번 성공적일 수는 없기에 개발된 피처를 제거해야 하는 경우 있습니다. 피처 패키징 기반으로 개발한다면 해당 기능을 컴포넌트 별로 제거할 필요없이 단순히 상위 피처 패키지에서 삭제하면 됩니다. 이는 또한 클래스의 가독성과 피처에 대한 이해를 높이는데에도 도움이 됩니다.
의존성: 피처 패키징 기반의 개발은 의존성을 줄이는데에도 큰 도움이 됩니다. 대부분의 클래스는 피처 패키지 내부에서 의존성을 가지게 될 것이고, 일부 공통 코드들에 대해서만 외부 의존성을 갖게 될 것입니다. 그리고 일부 클래스에 대해서는 패키지 접근자를 통해 세부 제한을 할 수도 있습니다. 뿐만 아니라 피처 패키징 내에서 컴포넌트 혹은 레이어 단위로 재분할을 할 수도 있습니다. 예를 들어 MVP 패턴으로 피처 패키징을 구성한다면 View, Model 그리고 Presenter를 서브패키지로 컴포넌트화 할 수 있습니다. 이를 통해 가독성과 유지보수성이 높아지고 쉽게 컴포넌트를 분리할 수 있기에 효율성 역시 향상됩니다.
리패키징 과정
중복코드와 의존성을 최소화하기 위해 공용 피처의 클래스를 다음과 같이 최상위 패키지인 ‘common’ 에 배치했습니다.
com.yourcompany.common.feature1
com.yourcompany.common.feature2
...
피처 내에서만 사용되는 공통 클래스 같은 경우는 다음과 같이 피처 패키지 내에 위치시켰습니다.
com.yourcompany.feature1.common
com.yourcompany.feature1.sub-feature1
...
또한 최상위 패키지에 ‘domain’이라는 패키지를 만들어 하나의 패키지 당 하나의 도메인으로 구성했습니다. 최근까지도 쿠팡은검색, 로그인, 좋아요, 장바구니와 같이 도메인 별로 책임을 나누고 팀을 구성해 기능들을 개발하고 있습니다. 예를 들어, 한 팀이 검색 도메인을 담당한다면 ‘search’ 도메인에 있는 홈 검색, 지도 검색 및 자동 완성과 같은 모든 기능을 담당해야 했고 때문에 피처를 각각 재구성할 필요가 있었습니다.
패키징 작업을 진행할 때 알면 좋은 몇가지 유용한 팁을 공유드립니다.
- 안드로이드 스튜디오의 내부 리팩토링 툴 사용. 리패키징 XML(Manifest, Layout 등) 내에 선언되어 있는 파일 혹은 클래스들의 경로를 잘 확인합니다. 이런 부분들은 Android Studio가 제공하는 리팩토링 툴을 활용해 업데이트할 수 있으므로 해당 기능을 사용하면 좀 더 안전하고 편하게 적용할 수 있습니다.
- ProGuard 설정을 업데이트할 때 Marker 인터페이스 사용. 패키지 업데이트를 할 때마다 매번 ProGuard 재설정을 하지 않기 위해 Marker 인터페이스를 생성해 이를 모든 클래스에 적용할 수 있습니다. 예를 들어, 모든 DTO에 DTO 인터페이스를 만들어 해당 인터페이스를 구현하는 모든 클래스의 ProGuard 설정을 단일화할 수 있습니다.
Interface:
public interface DTO {}ProGuard:
-keep class * implements com.yourcompany.common.DTO { *; }
- 가시화 또는 분석 툴을 통해 리패키징 전후 비교. 리패키징을 통해 얻은 혜택들을 가시화하기 위한 툴이 있습니다. Code Iris는 Android Studio 플러그인으로 모듈과 패키지 그리고 클래스의 의존성 그래프로 표현합니다. APK Dependency Graph는 클래스 간 커플링 상태를 보여줍니다. MetricsReloaded 의존성이 있는 패키지와 클래스를 분석할 수 있습니다.
결론
의존성을 완전히 없앨 수는 없지만 위에 있는 그림이 저희의 리패키징 작업이 만들어낸 차이를 잘 보여준다고 생각합니다. 이번 리패키징 프로젝트를 통해 확장성, 제거 용이성, 가독성이 향상되었을 뿐만 아니라, 공통 클래스도 여러 도메인에서 쉽게 식별하고 쉽게 재사용할 수 있게 되었습니다. 여전히 해야 할 작업은 많지만 모듈화의 큰 첫번째 단계가 성공적으로 완료되었습니다.
열린 사고로 질문하고 도전하며 역할에 책임을 다하는 인재는 언제나 환영합니다. 이 포스트가 재미있었다면 Coupang Careers에서 현재 채용 중인 포지션을 확인해 보세요!