모듈 목적에 맞게 나누기
단일 패키지 실험과 멀티 패키지로의 회귀
최근 isowords 프로젝트를 보며
기존의 멀티 패키지 구조를 단일 패키지 기반으로 병합해보기로 했다.
선택의 이유는 단순했다.
- 패키지가 많아질수록 관리 포인트가 늘어난다
- 단일 패키지로 합치면 구조 파악과 관리가 더 쉬워지지 않을까?
- 디렉토리 구조를 활용해, 다른 언어처럼 파일 시스템 기반 import를 유기적으로 활용할 수 있지 않을까?
결론부터 말하면, 실패했다.
⚠️ 단일 패키지의 함정: 복잡도의 폭발
막상 단일 패키지로 합쳐보니
관리 포인트는 줄어들었지만, 복잡도는 기하급수적으로 증가했다.
- 의존성 경계가 흐려진다
- 모듈 간 책임이 코드 레벨에서 강제되지 않는다
- “여기까지가 이 영역”이라는 감각이 점점 사라진다
특히 기대했던
“디렉토리 기반으로 유기적인 import가 가능하지 않을까?”
라는 가설은 현실과 맞지 않았다.
SwiftPM은 결국 Target 단위의 의존성 관리가 핵심이고,
디렉토리는 그저 정리 수단일 뿐이었다.
🔄 다시 멀티 패키지로
그래서 다시 멀티 패키지 구조로 돌아가기로 했다.
다만, 이전과는 기준이 조금 달라졌다.
🎯 기준 1: Feature는 단일 패키지
Feature 단위는 여전히 하나의 패키지로 유지한다.
Feature 내부에서의 응집도가 가장 중요하다고 판단했기 때문이다.
🎯 기준 2: 외부 의존성이 강한 영역부터 분리
특히 다음과 같은 패키지들은 거의 코드 변경이 없고,
플랫폼 성격이 강했다.
- 광고 패키지
- 플랫폼 의존 패키지
- 인증(Auth) 관련 패키지
이런 패키지들은
- 외부 라이브러리 의존성이 강하고
- 다른 Feature들과의 결합도가 낮으며
- 변경 가능성도 낮다
그래서 패키지로 분리해 격리시키는 것이 더 합리적이었다.
🌱 AppFeature와 Composition Root의 고민
또 하나 고민이 있던 지점은AppFeature라는 루트 역할 + Composition Root 역할을 하는 모듈이었다.
현재 서비스는
- Dev
- Stage
- Production
멀티 타겟 환경으로 운영 중이다.
문제는 이 환경 차이를Interface / Live 구조로 나누기 애매한 영역이 점점 늘어난다는 점이었다.
💡 App 전용 패키지로의 분리 아이디어
그래서 든 생각은 이거였다.
App 자체를 하나의 패키지로 보고
환경별 모듈을 그 안에서 명시적으로 나누면 어떨까?
구조는 대략 이런 느낌이다.
.target(
name: "AppFeature",
dependencies: [
/* App 공통 Feature */
]
),
.target(
name: "AppDev",
dependencies: [
"AppFeature",
/* Dev 전용 모듈 */
]
),
// AppStage, AppProduction...
이렇게 하면 얻을 수 있는 장점은 꽤 명확하다.
- 환경별 의존성이 코드 레벨에서 분리된다
- Dev 전용 도구(Playbook, Debug UI 등)는
AppDev에만 존재 - Stage / Production은 더 깔끔한 의존성 그래프 유지
- Composition Root의 책임이 명확해진다
🧠 모듈은 기술이 아니라 의도다
이번 구조 변경을 하면서 다시 느낀 점은 이거다.
모듈은 “나누는 기술”이 아니라
의도를 강제하는 장치다.
- 거의 변하지 않는 영역은 과감히 격리하고
- 자주 변하는 영역은 응집도를 높이고
- 환경 차이는 구조로 드러나게 만든다
단일 패키지가 항상 정답은 아니고,
멀티 패키지도 무조건 복잡한 건 아니다.
중요한 건, 모듈이 왜 존재하는지 스스로 설명할 수 있느냐인 것 같다.
이 구조도 언젠가는 다시 바뀔 수 있겠지만,
적어도 지금 기준에서는 꽤 합리적인 선택이라고 느낀다.