FSD 도입기: 같은 아키텍처, 다른 결과
도메인이 얕으면 짐이 되고, 깊으면 빛을 발한다.
두 프로젝트의 다른 결과, 그리고 그 사이에서 배운 것들
들어가며
프론트엔드 아키텍처에 대한 고민은 프로젝트 규모가 커질수록 피할 수 없는 숙제입니다.
우리 팀 또한 사내 서비스의 프론트엔드를 고도화하면서 여러 문제에 직면했고, 그 해결책으로 Feature-Sliced Design(FSD) 아키텍처를 선택했습니다.
하지만 결과는 프로젝트의 성격에 따라 극명하게 갈렸습니다.
사용자 접점이 많은 홍보용 웹사이트에서는 기대에 미치지 못했고,
핵심 기능을 담당하는 특정 도메인이 포함된 프로덕트에서는 매우 성공적이었습니다.
이 글은 그 여정에 대한 솔직한 기록입니다.
도입 전, 우리가 마주한 문제들
1. 제각각인 폴더 관리
컴포넌트는 항상 components/ 폴더에 두었습니다. 문제는, 그 안에서의 정리 방식이 개발자마다 달랐다는 것입니다.
components/
├── common/ # A 개발자: "공통이니까 여기"
├── MainSection/ # B 개발자: "페이지 단위로 나누자"
├── ProductCard/ # C 개발자: "도메인별로 구분하자"
└── Button/ # D 개발자: "UI 단위로..."어떤 규칙도 없었습니다.
누군가는 페이지 단위로, 누군가는 도메인별로, 또 누군가는 그냥 만든 순서대로 폴더를 만들었습니다.
”이 컴포넌트 어디 있어요?”라는 질문에 “그거 아마 components 어딘가에…”라고 답하는 일이 잦아졌습니다.
2. 순환 의존성 문제
명확한 레이어 구분이 없다 보니 컴포넌트 간 의존성이 꼬이는 경우가 잦았습니다.
CoreCard가 SubBanner를 임포트하고, 그 안에서 다시 ActionButton을 임포트하는 식이었습니다.
// 이런 일이 자주 일어났습니다
import SubBanner from '@/components/module-a/Banner';
3. 재사용성의 한계
“공통 컴포넌트”라고 만들었지만, 특정 도메인 로직이 섞여 있어 실제로는 재사용이 어려운 경우가 많았습니다. 비즈니스 로직과 UI의 분리가 명확하지 않았기 때문입니다.
4. 테스트 가능성 저하
의존성이 복잡하게 얽히다 보니 단위 테스트를 작성하기도 어려웠습니다.
하나의 컴포넌트를 테스트하려면 관련된 5~6개의 의존성을 함께 모킹해야 했습니다.
FSD를 선택한 이유
위 문제들을 해결할 수 있을 거라 기대하며 FSD를 선택했습니다.
| 문제 | FSD가 해결해줄 거라 기대한 것 |
|---|---|
| 제각각인 폴더 관리 | 레이어 기반 구조로 “어디에 둘지” 명확해짐 |
| 순환 의존성 | 단방향 의존성 규칙으로 강제 |
| 재사용성 한계 | shared/entities 분리로 비즈니스 로직과 UI 분리 |
| 테스트 어려움 | 레이어별 독립성으로 모킹 최소화 |
사례 1: 홍보용 웹사이트 - 러닝커브와 도메인의 벽
도입 초기: features vs entities 구분의 어려움
FSD를 홍보용 사이트 프로젝트에 처음 적용하면서 가장 어려웠던 점은 features와 entities의 구분이었습니다.
// 이 InteractionButton은 features일까, entities일까?
// entities/domain-a/ui/InteractionButton.tsx ? ❌
// features/domain-a/ui/InteractionButton.tsx ? 🤔
이론적으로는 명확해 보였지만, 실제 코드를 작성하다 보면 경계가 모호한 경우가 많았습니다. 팀원들과의 논의도 길어졌고, 러닝커브가 생각보다 높았습니다.
왜 이렇게 헷갈릴까? 어느 정도 개발이 진행된 시점에 레이어별 컴포넌트 분포를 분석해봤습니다.
widgets: 100개 이상 ...
features: 10여 개 ...
entities: 50여 개 ...- widgets이 압도적으로 많다는 것이 문제의 핵심이었습니다.
홍보용 웹사이트는 마케팅 특성상 퍼블리싱과 단순 인터랙션 위주의 페이지 가 대부분이었습니다.
복잡한 비즈니스 로직보다는 “컨텐츠를 사용자에게 보다 더 감각적으로 보여주는 것”이 주된 목표였죠.
// 전형적인 페이지 구조
export default function ContentPage() {
return (
<div>
<HeroWidget />
<FeatureSectionWidget />
<FAQWidget />
<ContactWidget />
</div>
);
}결과적으로 pages에 widgets만 나열하는 형태 가 되었습니다.
features 레이어는 거의 활용되지 않았고, entities도 API 호출 정도만 담당하는 얇은 레이어가 되었습니다.
”이게 FSD가 맞나?” 하는 의문
FSD의 핵심은 복잡한 비즈니스 로직을 레이어로 잘 나누어 관리 하는 것입니다. 하지만 이 프로젝트는,
- 사용자 인터랙션이 많지 않음 (features 활용도 ↓)
- 도메인 로직이 단순함 (entities 활용도 ↓)
- 페이지별 독립적인 UI 구성이 주요 작업 (widgets만 ↑)
“아키텍처가 프로젝트 성격에 맞지 않는 건 아닐까?” 하는 고민이 들었습니다.
그래도 완전한 실패는 아니었습니다.
- 공통 컴포넌트 정리:
shared/ui에 재사용 가능한 컴포넌트를 명확히 분리 - 일관된 파일 구조: 팀원들이 파일을 찾기 쉬워짐
- 의존성 규칙: 단방향 의존성 원칙만큼은 지켜져 순환 참조가 사라짐
사례 2: 에디터 프로덕트 - 도메인이 명확하니 구조도 명확하다
특정 도메인을 가진 프로덕트를 에디터 프로덕트라고 칭하겠습니다.
에디터 프로젝트는 시작부터 달랐습니다.
복잡한 데이터 구조를 직접 핸들링 해야 하는 명확한 도메인이 있었기 때문입니다.
widgets: 20개 미만
features: 10여 개
entities: 20여 개홍보용 사이트와 비교하면 레이어가 균형있게 분포되어 있습니다.
왜 잘 맞았을까?
에디터에는 “element”, “layer”, “context” 같은 명확한 도메인 개념 이 있었습니다.
이게 자연스럽게 entities가 되었고, “정렬”, “스타일 편집” 같은 사용자 액션 이 features가 되었습니다.
entities/element → features/alignment → widgets/toolbar
(데이터) (액션) (조합)“이 코드 어디에 둬야 하지?”라는 고민이 거의 없었습니다.
도메인 자체가 레이어 구분을 알려주는 느낌이었어요.
홍보용 사이트에서 헷갈렸던 features vs entities 구분이,
에디터에서는 “데이터냐 액션이냐” 로 명확하게 나뉘었습니다.
팀원들 반응도 달라졌습니다. “왜 이렇게 나눴지?”가 아니라 “이렇게 나누니 관리가 쉽네” 라는 반응이 나왔습니다.
회고: 아키텍처는 은탄환이 아니다
배운 것
1. 아키텍처는 도메인에 맞아야 한다
FSD는 훌륭한 아키텍처지만, 모든 프로젝트에 맞는 것은 아닙니다.
- 단순 노출형 프로젝트 (사례 1): 과도한 레이어 분리가 오히려 부채가 될 수 있음
- 비즈니스 중심 프로덕트 (사례 2): FSD의 레이어 분리가 강력한 유지보수성을 제공
2. 러닝커브를 과소평가하지 말 것
“아키텍처 문서를 공유하면 되겠지”라고 생각했지만, 실제 기대와는 달랐습니다.
- Features vs Entities 구분에만 2~3주 소요
- 팀원들이 익숙해지기까지 1~2개월
- 컨벤션 정립까지 수많은 코드 리뷰
3. 도메인을 이해하는 것이 먼저
에디터 환경에서 FSD가 잘 어울린다고 생각한 이유는 책임이 명확했기 때문이라 생각합니다.
- “핵심 데이터”가 무엇인지 정의됨
- “각 모듈”의 역할이 독립적임
- “상태 관리”의 범위가 명확함
반면 홍보용 사이트에서 어려웠던 이유는 도메인 계층이 얇았던 점 이 가장 크다고 생각해요.
- “핵심 도메인”과 “단순 UI”의 경계가 모호함
- “기능”보다는 “레이아웃” 위주의 작업이 많음
마치며
FSD가 좋아 보여서, 트렌디해 보여서, 적용하면 뭔가 달라질 것 같아서 도입했습니다.
돌이켜보면 “우리 프로젝트에 맞는가?”는 깊게 고민하지 않았던 것 같습니다.
결국 아키텍처는 문제를 해결하는 도구일 뿐입니다.
도메인이 복잡하면 FSD가 빛을 발하고, 단순하면 오히려 짐이 됩니다.
좋은 아키텍처는 따로 없습니다. 우리 문제에 맞는 아키텍처가 좋은 아키텍처입니다.