FSD에 대해 알아보기전에 지금까지 저는 어떻게 폴더구조를 정의해왔는지 얘기해보겠습니다.
주로 다음과 같은 구조로 작성해왔습니다.
- apis: api 호출
- components: 앱 전역에서 공통으로 사용되는 컴포넌트, 비즈니스 로직은 들어가면안되고 순수 ui 컴포넌트
- hooks: 앱 전역에서 사용되는 hooks
- constant: 앱 전역에서 사용되는 상수
- features: 각 도메인별 컴포넌트들을 모아놓은곳, 비즈니스로직도 포함되어있음
- pages: 페이지 컴포넌트
- stores: zustand로 각 도메인별 store정의
- utils: 앱 전역에서 사용되는 유틸 함수
├── apis/
├── components/
| ├── Button
| ├── Toast
| ├── Tooltip
| ├── Input
├── hooks/
| ├── useOutsideClick
| ├── useNetwork
| ├── querys
| | ├── useTemplateList
├── constant/
| ├── common
├── features/
| ├── message
| ├── template
├── pages/
| ├── Main
| ├── Help
| ├── SimpleChat
├── stores/
| ├── useChatStore
| ├── useTemplateStore
| ├── useAppStore
├── utils/
| ├── date.ts
| ├── string.ts
이렇게 구현했을때 제가 느낀 장단점은 다음과 같습니다.
장점
- 기능별(api/hooks/stores등..)로는 찾기가 수월하다.
- 폴더 계층이 깊지 않아서 금방 찾는다.
- type파일들은 api 타입은 api폴더에, features 각 도메인에서 사용되는 타입은 각 파일 옆에 두었습니다.
단점
- features가 점점 비대해진다.
- features에 도메인별로 폴더를 구성해뒀는데 각 도메인에서 쓰이는 반복적인 코드들을 정의하기가 애매했다.
- hooks에 특정 도메인에만 쓰이는 hook도 모두 포함되는 구조가 되버렸습니다. hook의 갯수가 엄청 많아지면 찾기 힘들어질수도 있습니다.
사실 앱의 기능이 그리 많지않으면 이 구조도 그렇게 나쁘지는 않았습니다. 이것저것 찾고 정의할때 무리는 없었습니다. 하지만, 앱을 혼자 개발한것이아닌 협업으로 개발했다면 추상화가 잘 되어있지않고 계층이 분리되어있지 않아서 협업이 힘든 점이 있을 것 같습니다. 여기저기서 store의 값들을 갖다쓰고 변화시키다보니 추적하기 어려운 면도 있습니다.
이제 FSD를 알아보겠습니다.
FSD란?
Feature Sliced Design의 약자로 기능 분할 설계 아키텍쳐입니다.
FSD 구성 요소
- Layers: 상위 레벨에 있는 레이어는 하위 레벨을 가질 수 있지만 하위 레벨에 있는 레이어는 상위 레벨 레이어를 가질 수 없습니다. 즉, 하위 레이어로 갈수록 추상화가 이루어집니다.
- Slices: slice는 도메인 이름으로 작성하여 비즈니스 로직을 작성할 수 있습니다. app과 shared에서는 비즈니스 로직을 작성하지 않기때문에 slice가 없습니다. slice내부에서는 segement를 가질 수 있습니다. 반드시 public api를 정의해서 필요한 것만 export하도록 합니다.
- Segments: slice에 작성된 도메인안에서 ui, model, lib, api등을 정의합니다.

Layers 구성 요소
IT-Bookstore라는 예제와 함께 Layers 구성 요소를 알아봅시다.
app
- 가장 상위 레이어로 재사용되는곳은 없습니다.
- provider, router, layout, style같은 전역 스타일, 전역 타입을 정의합니다.
- 애플리케이션의 진입점 역할
- bookstore 예제를 보면 layout, providers, routers, styles, types로 구성되어있습니다.
[layout] header, footer, toast, loading skeleton, scroll등 모든 페이지에 기본으로 구성되는 layout이 작성되어 있습니다.
[provider] 앱의 전역에 구성되어야하는 Provider를 정의합니다. errorboundary, theme 등이 있습니다.
[routers] 페이지의 router들을 구성합니다.
[styles] 전역 테마 색상을 정의합니다.
[types] app폴더에서 사용되는 전역 타입을 정의합니다.

pages
- entities, features, widgets으로부터 구성된 페이지
- app/routers에서 호출되는 페이지들입니다.
- bookstore예제에서는 메인페이지(homePage), 책 검색결과(searchResults), 책 알파벳 카테고리(categories) , 카테고리별 책 리스트 (category), 장바구니(cart), 책 상세페이지 (bookDescription) 로 구성되어있습니다.

widgets
- entities와 features를 결합하여 페이지에 사용되는 독립적인 UI 컴포넌트
- widgets은 웹 페이지의 도메인에 특화되어있는 컴포넌트들이 정의되어있어 재사용하기에는 어려운 면이 있습니다.
- bookStore에서는 header와 footer, 책 리스트 페이지네이션(bookListPagination), 메인페이지에 있는 책 슬라이드(slider), 책 상세 페이지(bookDetails), 메인페이지에 있는 책 카테고리별 리스트(productCategory)로 구성되어있습니다.

features
- 유저 인터랙션이나 비즈니스 로직을 유저에게 전달하는 역할을 합니다.
- bookstore에서는 책 검색(search), 장바구니에 넣기(addToCart)가 있습니다.

entities
- 비즈니스 엔티티
- bookstore에서는 책 저자표시(author/ui), 책 상세페이지 모델(bookDescription/model), 책 목록 모델(bookList/model), 책 미리보기(bookPreview/ui), 카트 미리보기(cartPreview/ui, model), 책 카테고리 미리보기 모델(categoryPreview/model), 테마변경 등이 있습니다.

shared
- 비즈니스 로직에 종속되지 않은 재사용 가능한 컴포넌트와 유틸리티
- 재사용가능한 함수, api, 상수, store 등이 포함됩니다.
- bookstore에서는 api(책정보, 카테고리별 책 목록, 검색결과), assets(아이콘, 이미지), consts(상수), lib(유틸함수, 공통 hook 등), types, ui가 정의되어있습니다.

이렇게 레이어들을 구성하면 유지보수가 용이하고 확장 가능한 아키텍쳐를 설계할 수 있습니다.
이와 같은 설계를 하면 OOP(Object Oriented Programming)의 원칙들을 프론트엔드에도 적용할 수 있습니다.
낮은 레이어는 더 추상화 되어 있으므로 더 높은 레이어에서 재사용될 수 있고(추상화, 상속), 매개변수나 props에 따라 컴포넌트나 기능이 다르게 작동할 수 있습니다.(다형성)
Public API로 필요한것만 공개시키고 슬라이스 내부에서만 사용되는것은 외부에서 접근할 수 없으므로 캡슐화가 달성됩니다.
출처
FSD 공식 홈페이지
https://feature-sliced.design/
FSD 예제들
https://feature-sliced.design/examples