모듈 목적에 맞게 나누기

단일 패키지 실험과 멀티 패키지로의 회귀

최근 isowords 프로젝트를 보며
기존의 멀티 패키지 구조를 단일 패키지 기반으로 병합해보기로 했다.

선택의 이유는 단순했다.

결론부터 말하면, 실패했다.


⚠️ 단일 패키지의 함정: 복잡도의 폭발

막상 단일 패키지로 합쳐보니
관리 포인트는 줄어들었지만, 복잡도는 기하급수적으로 증가했다.

특히 기대했던

“디렉토리 기반으로 유기적인 import가 가능하지 않을까?”

라는 가설은 현실과 맞지 않았다.

SwiftPM은 결국 Target 단위의 의존성 관리가 핵심이고,
디렉토리는 그저 정리 수단일 뿐이었다.


🔄 다시 멀티 패키지로

그래서 다시 멀티 패키지 구조로 돌아가기로 했다.

다만, 이전과는 기준이 조금 달라졌다.

🎯 기준 1: Feature는 단일 패키지

Feature 단위는 여전히 하나의 패키지로 유지한다.
Feature 내부에서의 응집도가 가장 중요하다고 판단했기 때문이다.

🎯 기준 2: 외부 의존성이 강한 영역부터 분리

특히 다음과 같은 패키지들은 거의 코드 변경이 없고,
플랫폼 성격이 강했다.

이런 패키지들은

그래서 패키지로 분리해 격리시키는 것이 더 합리적이었다.


🌱 AppFeature와 Composition Root의 고민

또 하나 고민이 있던 지점은
AppFeature라는 루트 역할 + Composition Root 역할을 하는 모듈이었다.

현재 서비스는

멀티 타겟 환경으로 운영 중이다.

문제는 이 환경 차이를
Interface / Live 구조로 나누기 애매한 영역이 점점 늘어난다는 점이었다.


💡 App 전용 패키지로의 분리 아이디어

그래서 든 생각은 이거였다.

App 자체를 하나의 패키지로 보고
환경별 모듈을 그 안에서 명시적으로 나누면 어떨까?

구조는 대략 이런 느낌이다.

.target(
  name: "AppFeature",
  dependencies: [
    /* App 공통 Feature */
  ]
),
.target(
  name: "AppDev",
  dependencies: [
    "AppFeature",
    /* Dev 전용 모듈 */
  ]
),
// AppStage, AppProduction...

이렇게 하면 얻을 수 있는 장점은 꽤 명확하다.


🧠 모듈은 기술이 아니라 의도다

이번 구조 변경을 하면서 다시 느낀 점은 이거다.

모듈은 “나누는 기술”이 아니라
의도를 강제하는 장치다.

단일 패키지가 항상 정답은 아니고,
멀티 패키지도 무조건 복잡한 건 아니다.

중요한 건, 모듈이 왜 존재하는지 스스로 설명할 수 있느냐인 것 같다.

이 구조도 언젠가는 다시 바뀔 수 있겠지만,
적어도 지금 기준에서는 꽤 합리적인 선택이라고 느낀다.