
애플리케이션을 기능과 책임 중심으로 분리하여 구성하는 아키텍처 패턴입니다.
기존의 역할 중심 구조와 달리 비즈니스 관점에서 의미 있는 단위를 기준으로 코드를 구성하는 것이 특징으로
FSD는 다음과 같은 레이어 구조를 기반으로 합니다.
shared : 전역에서 재사용 가능한 공통 리소스
entities : 비즈니스 엔티티 단위의 상태와 표현
features : 사용자 행동 단위의 기능
widgets : 여러 기능을 조합한 UI 블록
pages : 실제 화면 단위의 페이지 구성
app : 애플리케이션 초기화 및 전역 설정
각 레이어는 상위 레이어에서만 하위 레이어를 참조하도록 설계되며
의존성 방향이 명확하게 유지됩니다.
기존 역할별 구조에서 다음 문제가 발생할 수 있었습니다.
의존성 불명확 : 컴포넌트 간 의존성이 복잡해 영향 범위 파악이 어려움
코드 재사용 어려움 : 도메인 로직 혼재로 재사용이 불편함
확장성 부족 : Electron 추가 등 스프린트에서 코드 공유 전략 부재
FSD 아키텍처를 도입한 이유는 단순히 폴더 구조를 정리하기 위함이 아니라 프로젝트의 확정성, 유지보수성, 협업의 효율성을 장기적으로 고려한 선택이었습니다. 초기 개발 단계에서 FSD 구조가 아닌 기존에 많이 사용하던 역할별 구조로 빠른 MVP를 뽑아낼 수 있었지만 기능이 추가되고 도메인의 관계가 복잡해질수록 컴포넌트와 비즈니스 로직의 경계가 흐려지고 코드를 변경할 때 영향이 미치는 범위를 파악하는데 문제가 발생할 수 있다고 판단하였습니다.
특히 본 프로젝트는 앱잼 이후에도 추가 스프린트를 통해 기능 확장 및 고도화를 계획하고 있으며
Electron 환경의 확장 등 여러 환경에서의 코드 공유 가능성 또한 고려 대상이었습니다.
이러한 상황에서 기능과 책임이 명확하게 분리되지 않은 구조는 장기적으로 생각했을때 구조적인 부담으로 작용할 수 있다고 판단하였습니다.
또한 모노레포 환경에서 여러 패키지와 애플리케이션을 함꼐 관리하는 만큼
각 프로젝트마다 일관된 아키텍쳐 규칙을 기반으로 한 공통 구조가 필요하다고 생각하였습니다.
FSD의 장점이 해당 프로젝트의 여러 요구사항에 부합하였습니다.
이러한 이유로 본 프로젝트에서는 확장성과 유지보수를 우선적으로 고려하여 FSD 아키텍처를 선택하게 되었습니다.
apps/web/src/
├── app/
├── pages/ # pages 레이어
│ ├── dashboard/
│ ├── roadmap/
│ ├── eligibility/
│ └── my-page/
├── widgets/ # widgets 레이어
│ ├── dashboard/
│ ├── roadmap/
│ ├── eligibility/
│ └── my-page/
├── features/ # features 레이어
├── entities/ # entities 레이어
└── shared/ # shared 레이어
├── ui/ # 비즈니스 로직 없는 UI
├── utils/ # 유틸리티 함수
├── types/ # 공통 타입
├── hooks/ # 공통 훅
├── constants/ # 공통 상수
├── config/ # 앱 설정
├── apis/ # API 클라이언트
├── router/ # 라우터 설정
├── query/ # React Query 설정
└── layouts/ # 레이아웃 컴포넌트
packages/
├── kds-ui/ # 디자인 시스템 (원자적 UI 컴포넌트)
│ └── src/
│ ├── components/ # Button, Input, Card 등
│ └── styles/ # Vanilla Extract 스타일
└── configs/ # 공유 설정
pages 레이어
페이지 컴포넌트 작성
widgets와 features를 조합해 페이지 구성
widgets 레이어
독립적인 UI 블록 개발
여러 features를 조합한 복합 컴포넌트
구조: components/, configs/, constants/, hooks/, types/, utils/
features 레이어
사용자 기능 단위 개발
비즈니스 로직과 UI 포함
entities 레이어
비즈니스 엔티티 정의 및 관리
엔티티별 타입, API, UI 컴포넌트
shared 레이어
앱 전역 공유 리소스
ui/: 비즈니스 로직 없는 UI
utils/: 순수 함수
types/: 공통 타입
hooks/: 공통 훅
constants/: 공통 상수
config/: 앱 설정
apis/: API 클라이언트
router/: 라우터 설정
query/: React Query 설정
layouts/: 레이아웃 컴포넌트
FSD의 모든 레이어를 도입하면서 기존 개발에 익숙한 구조로 변경했습니다.
변경 사항
FSD 표준: ui/, model/, lib/
프로젝트 적용: components/, configs/, constants/, hooks/, types/, utils/
변경 이유
직관성 : 폴더명만으로 역할 파악 가능
익숙한 구조 : 익숙한 구조로 러닝커브 감소
세분화 : 역할별 폴더 분리로 관리 용이
해당 이슈에 대해서 정말 많이 고민하였습니다... FSD 아키텍처 구조로 세팅을 하는 과정에서 모노레포 환경의 @ksd/ui 패키지와 FSD 아키텍처의 shared/ui 레이어의 역할이 겹치는 문제가 발생할 수 있다고 판단하였습니다.
두 곳 모두 UI 컴포넌트를 관리하는데 어디에 무엇을 배치해야 할지 명확하지 않았습니다.
그래서 3가지 방안을 고려하게 되었습니다.
1. 모노레포의 @ksd/ui 패키지에서 전부 관리
장점 : 한 곳에서 관리하여 로직이 명확한 장점이 있음
단점 : FSD의 shared/ui 레이어의 존재가 무의미하여 엄격한 FSD 아키텍처 구현에 있어서 방향성과 다르다고 판단
2. Re-export 패턴
패키지에 모든 컴포넌트를 두고 shared/ui에서 다시 export하는 방식
장점 : FSD의 shared/ui를 살리면서 import 경로를 통일
단점 : 왜 굳이 두번을 거쳐야 하는지에 대한 혼란 유발 가능성
3. 역할 분리
@ksd/ui 패키지 : 순수 원자 ui 포넌트
shared/ui : 비즈니스 로직을 포함하지 않는 ui 컴포넌트
entities/features : 비즈니스 로직을 포함하는 ui 컴포넌트
장점 : 명확한 기준을 가지며 FSD 원칙 유지할 수 있음
단점 : 비즈니스 컴포넌트를 분리하는 과정에서 혼란 유발 가능
최종적으로 모노레포의 @kds/ui와 FSD 아키텍처의 엄격한 구조를 동시에 챙기며 비즈니스 컴포넌트 로직을 분리할 수 있는
역할을 분리하는 방안을 선택하였습니다.
위치 : packages/kds-ui/src/components/
역할 : 디자인 시스템의 원자적 컴포넌트
특징 : 독립적인 도메인을 가지며 비즈니스 로직 없음
위치 : apps/web/src/shared/ui/
특징 : 원자 컴포넌트의 조합으로 비즈니스 로직 없는 UI 컴포넌트
예시 : FormField, SearchInput, PageHeader, Container, Grid
역할 : 비즈니스 로직이 포함된 UI