안녕하세요! 앱 개발팀의 레이몬드입니다. 2024년에 진행했던 모듈화 여정을 1년 동안의 iOS 모듈화 진행기로 공유했었는데요, 이번에는 그 여정의 다음 단계인 Component 모듈에 대해 이야기해보려 합니다.
지난 글에서는 모놀리식 구조에서 출발해 Core 레이어를 분리하고, 의존성을 정리해 나가는 과정을 다뤘습니다. 이번 글에서는 그 위에 쌓인 다음 단계, 즉 공통 UI 컴포넌트를 모듈로 분리하고, 스토리북·스냅샷 테스트까지 엮어 “관리 가능한 컴포넌트 체계”를 만든 과정을 공유하려고 합니다.
단순히 모듈을 나누는 것을 넘어서서,
까지 정리해 보겠습니다.
제 개인적인 모듈화의 궁극적인 목표는 피처 모듈 분리와 데모앱을 통한 생산성 향상입니다. 하지만 현실에서는 피처 모듈들이 공통 UI 컴포넌트에 강하게 의존하고 있었습니다.
지그재그 앱에는 다음과 같은 공통 UI 컴포넌트들이 다수 존재했습니다.

이런 컴포넌트들이 여러 피처에서 얽혀서 사용되고 있었고, 이 상태에서는 피처 모듈을 독립적으로 분리하기가 어려웠습니다. 결과적으로 우리는 다음과 같은 결론에 도달했습니다.
피처 모듈을 온전히 분리하려면, 먼저 공통 UI 컴포넌트들을 독립된 Component 모듈로 분리해야 한다.

여러 지면에서 동일한 컴포넌트를 공유하고 있기 때문에, 이 레이어를 먼저 정리하지 않으면 피처 단에서의 모듈 분리는 항상 어딘가 걸려버리곤 했습니다.
지그재그 앱의 주요 지면은 서버 드리븐 방식으로 운영되고 있습니다.
이 구조는 유연성을 주는 동시에, 다음과 같은 한계를 만들었습니다.
이로 인해 실제로 이런 문제가 반복해서 발생했습니다.

결과적으로, 서버 드리븐 구조 위에서 컴포넌트 카탈로그/스토리북의 부재가 비용으로 드러나고 있었습니다.
서버 드리븐 컴포넌트들은 내부적으로 공통 View나 공통 헤더·캐러셀을 공유하는 경우가 많아서 다음과 같은 상황도 자주 발생했습니다.

문제는 크게 세 가지였습니다.
“수정은 했는데, 정확히 어디까지 검증해야 하는가?”가 항상 남는 질문이었습니다.
지그재그 앱의 서버 드리븐 컴포넌트들은 대부분 다음과 같은 구조를 가지고 있었습니다.

여기서 큰 도움이 되었던 것은, 이미 ZCore 모듈로 모델·Repository를 분리해 둔 상태였다는 점입니다.
과거 모듈화 여정에서 우리는 다음을 먼저 진행했습니다.
그래서 Component 모듈 분리는 크게 아래와 같은 작업으로 정리할 수 있었습니다.
public 접근 제어자 추가import ZComponent 추가물론 실제로는 파일 수가 많아 수고가 적지는 않았지만, “Core 분리가 선행된 상태” 덕분에 의존성 정리에 대한 불확실성은 많이 줄어든 상태에서 작업을 시작할 수 있었습니다.
모듈 분리를 하다 보면, 단순히 파일을 옮기고 public을 붙였을 뿐인데도 수백 줄의 diff가 생기곤 합니다. 이 상태에서 로직 변경까지 섞이면 리뷰어 입장에서 중요한 변경을 놓치기 쉬웠습니다.
그래서 다음과 같이 커밋을 나누는 전략을 사용했습니다.
public + import 정리 커밋
public 추가import ZComponent 추가이렇게 나누니 리뷰어 입장에서 “어디를 집중해서 봐야 하는지”가 훨씬 명확해졌고, 결과적으로 리뷰 속도와 품질이 함께 개선되었습니다.
앞서 말한 두 번째 문제, 즉 서버 드리븐 환경에서의 가시성 부족을 해결하기 위해 우리는 ZComponent 모듈에 **데모 타겟(스토리북 앱)**을 만들었습니다.

Component 스토리북의 목표는 명확했습니다.
ZComponent 모듈에 데모용 앱 타겟을 추가했습니다.
각 컴포넌트가 바인딩하는 모델 데이터를 JSON 파일로 케이스별 저장했습니다.
GoodsGroupCell_default.json, GoodsGroupCell_discounted.json …이를 통해 다음이 가능해졌습니다.
이 데모앱은 iOS 개발자뿐만 아니라, 팀 전체의 “컴포넌트 카탈로그” 역할을 하게 되었습니다.
이전에는 “누군가의 머릿속”에만 있던 정보가, 이제는 누구나 실행해볼 수 있는 앱으로 정리되었다는 점이 가장 큰 변화였습니다.
세 번째 문제였던 QA 범위 파악의 어려움을 해결하기 위해, 우리는 Component 모듈에 스냅샷 테스트를 도입했습니다.
Component 스냅샷 테스트의 목적은 명확합니다.
우리는 uber/ios-snapshot-test-case 라이브러리를 기반으로 스냅샷 테스트를 구현했습니다.

작동 방식은 다음과 같습니다.

덕분에 PR에 다음과 같은 대화가 가능해졌습니다.

PR 작성자와 코드 리뷰어는 실제 UI 기준으로 어떤 컴포넌트들이 영향을 받았는지를 PR 화면에서 바로 확인할 수 있게 되었고 QA에게 바로 전달할 수 있게 되었습니다.
Component 모듈, 데모앱, 스냅샷 테스트까지의 흐름을 통해, 처음에 이야기했던 세 가지 문제를 다음과 같이 정리할 수 있습니다.
모듈화 여정은 여전히 진행 중이고, 완벽한 정답을 찾았다고 보기는 어렵습니다. 다만 이번 Component 모듈 작업을 통해,
이 글이 비슷한 고민을 하고 있는 팀들에게 “이런 접근도 가능하구나” 정도의 참고가 되면 좋겠습니다.