기존 탭탭의 모듈화

기존 탭탭의 Feature 모듈은 모듈화의 목적을 충분히 살리지 못한 형태였습니다. 현재 구조는 각 Feature가 개별 모듈로 분리되어 있는 것이 아니라, Feature라는 하나의 모듈 내부에서 Feature별로 폴더링만 되어 있는 방식이었습니다. 즉, 물리적으로는 단일 모듈이며, 논리적으로만 피처가 구분된 구조였습니다.
이러한 구조에서는 모듈화가 제공하는 핵심적인 장점을 기대하기 어렵습니다. 예를 들어 특정 피처 하나만 수정하거나 테스트하고자 할 경우에도, 해당 피처만 빌드할 수 없고 Feature 모듈 전체를 빌드해야했습니다. 이는 불필요한 빌드 범위 확대로 이어져 빌드 시간 증가와 테스트 지연을 발생시켰습니다.
그 결과, 피처 단위의 독립적인 개발 및 검증이 어려웠고, 모듈화를 도입한 본래의 의도를 충분히 달성하지 못하고 있는 상황이었습니다.
이에 따라 각 피처를 명확한 단위의 모듈로 분리하고, 피처 단위로 독립적인 빌드와 테스트가 가능한 구조로 개선할 필요가 있다고 판단했습니다.
모듈 분리를 통해 기대하는 점
1. 독립적인 빌드와 테스트 가능
각 피쳐를 개별 모듈로 분리함으로써, 특정 피쳐만 선택적으로 빌드하거나 테스트할 수 있어 개발 속도를 향상시킬 수 있을 것으로 기대합니다. (예를 들어, SearchView 빌드하고 싶을 때.. 앱 진입점으로 가서 앱 진입점 수정하는 불편함...! 너무 컸어요...)
2. 빌드 속도 개선
각 피쳐를 개별 모듈로 분리함으로써, 특정 피쳐만 선택적으로 빌드하거나 빌드 속도를 향상시킬 수 있을 것으로 기대합니다.
3. 책임과 경계 명확화
모듈 단위로 책임을 정의함으로써, 기능별 의존성을 명확히 하고 코드 구조를 이해하기 쉽게 만들 수 있을 것이라 기대합니다.
Feature 모듈

더 이상 하나의 Feature 모듈 안에서 폴더링으로만 피처를 구분하지 않고, 각 피처를 물리적으로 분리된 모듈로 구성했습니다. 이제 탭탭의 피처는 각각 독립적인 모듈이 되었습니다.
자, 모듈 분리를 통해 우리가 기대했던 효과들이 있었습니다. 하나씩 살펴볼까요?
1. 독립적인 빌드와 테스트 가능

피처를 개별 모듈로 분리함에 따라, 이제는 피처 단위의 독립적인 빌드가 가능해졌습니다. 이전처럼 전체 Feature 모듈을 빌드할 필요 없이, 수정이 발생한 피처만 선택적으로 빌드하고 테스트할 수 있습니다.
더 나아가 'tuist generate 모듈명' 명령어를 통해 특정 피처 모듈만 별도로 분리하여 작업하는 것도 가능해졌습니다.
+ 탭탭은 각 피처 모듈에 예제 앱을 추가했습니다. 더 이상 메인 앱의 진입점에 접근해 코드를 수정하지 않아도, 해당 피처만 단독으로 실행 및 검증할 수 있게 되었습니다.


위와 같이 각 피처별로 독립적인 앱 진입점이 존재하기 때문에, 해당 피처에서 필요한 화면과 의존성만 구현해주면 메인 앱과 분리된 상태에서도 즉시 실행 및 테스트가 가능합니다. (드디어 메인 앱 진입점을 억지로 수정해가면서 확인할 필요가 없게 되었습니다.)
2. 빌드 속도 개선

개선 전 클린 빌드 기준 49.6초에서,

16.6초로 약 30초 정도 단축 되었습니다.
여기서 tuist cache를 활용해 변경이 적은 모듈들을 미리 빌드하여 캐시로 관리한다면, 빌드 시간을 더욱 단축할 수 있을 것으로 기대합니다.
3. 책임과 경계 명확화
를 이야기 하기 전에, 기존 탭탭에는 Domain이라는 모듈이 존재했습니다. 해당 모듈은 모델, 데이터 구조, extension, 상수 등 앱 전반에서 공통으로 사용되는 코드들을 모아둔 모듈입니다. 하지만 Domain이라는 이름은 클린 아키텍처에서의 Domain 레이어를 연상시키며, 실제 역할과는 다른 느낌(?)을 주는 문제가 있었습니다. 이름에서 오는 괴리감?
클린 아키텍처에서의 Domain은 비즈니스 규칙을 담당하는 계층이지만, 기존 탭탭의 Domain 모듈은 이러한 책임을 갖고 있지 않았습니다. 그 결과, 모듈의 실제 역할과 명칭 사이에 괴리가 발생했습니다.
그래서 결국 어떻게 됐냐면요.. Domain 모듈의 이름을 Core로 변경했습니다! Core 모듈은 앱의 핵심이 되는 데이터 모델과 데이터 관리 로직을 담당하도록 역할을 명확히 정의했습니다. 또, 앱 전반에서 공통적으로 사용되는 Dependency, 상수, extension 등을 관리하기 위해 Shared 모듈을 새롭게 분리했습니다.

이렇게 역할을 분리함으로써 각 모듈의 책임이 보다 명확해졌고, 모듈 간 의존성 또한 자연스럽게 정리할 수 있었습니다.
Dependency를 어떻게 할까?

TCA에서 의존성 관리를 위해 Dependency를 구현해 사용하고 있습니다. 개발 회의에서 의존성 또한 피처 단위로 분리하는 것이 모듈화의 방향성과 일치한다고 판단했고, 이에 따라 Dependency 역시 각 피처별로 개별 모듈화하기로 결정했습니다. 실제로 각 피처에 대응되는 Dependency 모듈을 분리하여 작업을 진행했습니다.
그러나 작업을 진행하면서, 피처별 Dependency 간에 중복되는 코드가 과도하게 많다는 문제를 확인하게 되었습니다. 스토리지/DB 접근, 공통 유틸성 의존성 등은 대부분 피처에서 거의 동일한 형태로 필요했기 때문에, 이를 피처별로 나누어 관리하는 것이 과연 합리적인지에 대한 의문이 들었습니다.
결과적으로, 피처별 Dependency 모듈 분리는 현재 프로젝트 규모에 비해 과도한 설계, 즉 오버엔지니어링에 가까운 선택이라는 결론에 도달했고, Dependency 역시 Shared 모듈에서 관리하도록 하였습니다.
'탭탭 - TapTap > 리팩토링' 카테고리의 다른 글
| [리팩토링] 08. SwiftDataClient 리팩토링 - SwiftDataClient의 비대함 완화 및 명확한 구분 (0) | 2026.02.12 |
|---|---|
| [리팩토링] 07. SwiftDataClient 리팩토링 - 중복 코드 제거 (0) | 2026.02.12 |
| [리팩토링] 05. Swift Concurrency로 메모리 누수와 Callback 지옥을 해결해보자 (온보딩 리팩토링). (0) | 2026.01.19 |
| [리팩토링] 03. 구조개선 - App진입점을 TCA로 컨버팅 (1) | 2026.01.15 |
| [리팩토링] 02. 탭탭의 구조 개선 (0) | 2026.01.15 |