Software Design

[모듈화 #3] Common 모듈이 쓰레기통이 되는 이유 (모듈 레이어 설계 총정리)

모듈화를 진행하는 팀 대부분이 어느 시점에 Common, Core, Utils 같은 이름의 모듈을 만듭니다.

이석우iOS Developer5분 읽기
[모듈화 #3] Common 모듈이 쓰레기통이 되는 이유 (모듈 레이어 설계 총정리) 대표 이미지

모듈화를 진행하는 팀 대부분이 어느 시점에 Common, Core, Utils 같은 이름의 모듈을 만듭니다.

“여러 곳에서 쓰니까 일단 여기에”라는 판단이 쌓이다 보면, 어느새 이 모듈은 프로젝트에서 가장 크고, 가장 자주 바뀌고, 모두가 의존하는 모듈이 되어 있어요.

결론부터 말씀드리면, 이름이 Common인 모듈은 응집도가 없다는 자백과 같습니다. “공용”은 코드를 묶는 기준이 될 수 없거든요.

이 글에서는 Common 모듈이 왜 모듈화를 되돌리는지, 그리고 이를 예방하는 레이어 구조(수직 분리)와 기능 구조(수평 분리)를 정리합니다.

공통 모듈이라는 이름의 쓰레기통, 커지는 건 순식간입니다
공통 모듈이라는 이름의 쓰레기통, 커지는 건 순식간입니다

Common 모듈은 왜 위험할까?

문제는 세 단계로 진행됩니다.

1단계, 쓰레기통화. “어디에 둘지 애매한 코드”가 전부 Common으로 들어갑니다. 날짜 포매터 옆에 네트워크 클라이언트가, 그 옆에 커스텀 버튼이 놓여요. 서로 아무 관련이 없죠.

2단계, 전원 의존. 모든 기능 모듈이 Common을 import하게 됩니다. 이 시점에 Common은 그래프에서 가장 하류, 즉 가장 안정적이어야 할 위치를 차지합니다.

3단계, 전체 재빌드. 그런데 Common은 쓰레기통이라 제일 자주 바뀝니다. 지난 글의 안정 의존 원칙이 정확히 뒤집힌 상태예요. 버튼 색 하나 바꿨는데 전 모듈이 다시 빌드됩니다.

가장 많이 의존받는 모듈이 가장 자주 바뀌는 모듈이 되는 순간, 모듈 경계는 있으나 마나 한 상태가 됩니다.


레이어 구조, 수직으로 나누기

예방책의 첫 축은 역할에 따른 수직 분리입니다. 흔히 쓰는 3~4단 구조는 이렇습니다.

레이어 담는 것 예시
Feature 화면·기능 단위 모듈 홈, 검색, 주문, 설정
Domain 업무 규칙·모델·유스케이스 주문 정책, 회원 모델
Core 특정 기술의 얇은 래퍼 네트워크, 저장소, 로깅
Shared 진짜 범용 유틸 날짜 포매터, 문자열 확장

규칙은 하나입니다. 의존은 위에서 아래로만 흐른다.

Feature는 Domain과 Core를 쓸 수 있지만 Core가 Feature를 알면 안 됩니다. Feature끼리도 서로 직접 참조하지 않고 상위 앱 타깃이 조립합니다.

의존은 위에서 아래로만, 레이어 규칙은 이 그림이 전부예요
의존은 위에서 아래로만, 레이어 규칙은 이 그림이 전부예요

여기서 Common과 뭐가 다른지가 중요합니다. Core는 하나의 거대 모듈이 아니라 기술 단위로 쪼갠 여러 모듈이에요.

  • CoreNetwork, CoreStorage, CoreLogging처럼 각각 독립 모듈로
  • 검색 기능이 로깅만 쓴다면 CoreLogging만 import

이러면 네트워크 코드를 고쳐도 로깅만 쓰던 모듈은 재빌드되지 않습니다.


기능 구조, 수평으로 나누기

두 번째 축은 도메인에 따른 수평 분리입니다. 같은 레이어 안에서도 홈·검색·주문은 서로 다른 모듈이어야 해요.

판별 기준은 첫 글에서 다룬 그 질문입니다. “이 코드들은 같은 이유로 바뀌는가?”

  • 검색 정책이 바뀔 때 주문 코드가 바뀔 일이 없다면, 둘은 다른 모듈입니다
  • 주문 화면과 주문 유스케이스가 항상 같이 바뀐다면, 수직으로 더 쪼개는 건 과할 수 있습니다

수직(레이어)과 수평(기능)을 겹치면 격자가 됩니다. 실제 모듈 하나는 “주문 기능의 Domain”, “검색 기능의 Feature”처럼 격자의 한 칸이 되는 거죠.


이미 Common이 비대해졌다면?

기존 Common을 한 번에 폭파할 필요는 없습니다. 실무에서 검증된 순서는 이렇습니다.

  1. 신규 반입 금지: 오늘부터 Common에 새 코드를 넣지 않는 규칙부터 세웁니다
  2. 사용처 조사: 가장 자주 바뀌는 코드부터, 실제로 어디서 쓰는지 확인합니다
  3. 제자리 찾기: 한 기능에서만 쓰면 그 기능 모듈로, 특정 기술 래퍼면 Core 계열 모듈로 이동합니다
  4. 남은 것 개명: 끝까지 남는 진짜 범용 코드만 Shared처럼 좁은 이름을 붙여 격리합니다

수개월 걸리는 작업이지만 단계마다 재빌드 범위가 눈에 띄게 줄어드는 걸 확인할 수 있습니다.

정리의 시작은 새로 안 넣는 것부터입니다
정리의 시작은 새로 안 넣는 것부터입니다

언제 이 구조가 필요하고 언제 과할까?

  • 개발자 4~5명 이상, 기능 영역이 3개 이상이면: 레이어 규칙을 세울 가치가 충분합니다
  • 1~2인 프로젝트면: Feature/Shared 2단 정도로 시작해도 됩니다. 격자부터 그리는 건 과합니다
  • 어느 규모든: “Common·Utils라는 이름의 모듈을 만들지 않는다”는 규칙만은 처음부터 지키는 걸 권합니다

면접에서는 이렇게 물어봅니다

Q. 공통 모듈(Common)이 커지면 어떤 문제가 생기고, 어떻게 설계하는 게 좋을까요?

모두가 의존하는 모듈이 가장 자주 바뀌는 모듈이 되어, 작은 수정에도 전체 재빌드와 광범위한 영향 검토가 필요해집니다. 안정 의존 원칙이 뒤집힌 상태입니다. 역할별(네트워크·저장소·로깅)로 잘게 나누고 진짜 범용 코드만 최소한의 Shared 모듈에 격리하는 방식으로 설계합니다.

Q. 모듈 구조를 레이어로 나눌 때의 규칙을 설명해 보세요.

Feature, Domain, Core처럼 역할별 레이어를 두고 의존은 위에서 아래 한 방향으로만 허용합니다. 같은 레이어의 모듈끼리는 직접 참조하지 않고 상위 조립 계층에서 연결합니다. 이 규칙이 지켜지면 변경의 영향 범위가 항상 위쪽으로만 한정됩니다.


여기까지가 모듈화의 원론 3부작이었습니다. 경계 세우기, 의존 방향, 레이어 구조까지 왔으면 개념 도구는 갖춰진 셈이에요.

다음 글부터는 이 원칙들을 iOS 프로젝트에 그대로 적용합니다. Swift Package로 모듈을 실제로 나누는 법부터 시작할게요.