개발을 진행하면서 좋은 구조로 어떻게 짤 수 있을까가 항상 고민이다.
그 와중에 FSD 아키텍쳐에 대해 알게 되었고 해당 아키텍쳐에 대해 알아보고자 한다.
추가로 Next.js에서 어떻게 적용할 수 있을지 확인해보자.
FSD는 Feature-Sliced Design의 약자로서 기능 분할 설계를 의미한다. 애플리케이션을 기능 단위로 조직화하여 관리하는 아키텍쳐이다.
기능 단위로 분리하면 개발 및 유지보수를 용이하게 한다.
점진적으로 소프트웨어 규모가 커질 예정이라면 FSD를 적용할 수 있을 것으로 예상된다.
해당 아키텍쳐는 다음과 같은 3가지의 개념으로 나눠져있다.

1. Layers
2. Slices
3. Segments
각각 살펴보자.


Layer는 최상위 디렉토리이다. 최대 7개로 구성되어 있는데 processes는 더 이상 사용되지 않는다고 한다. 그리고 각 디렉토리 별로 옵셔널하게 사용할 수 있다.
상위 레벨에 있는 레이어는 하위 레벨을 의존성으로 가질 수 있지만 그 반대는 성립될 수 없다.
하위 레이어로 갈수록 추상화가 심화되며 상위 레이어로 갈수록 비즈니스 로직이 심화된다.
따라서 계층이 낮을수록 추상화 수준이 높아지기 때문에 재상용성이 높아지고 자율성이 낮아진다.
어플리케이션 로직이 초기화되는 곳이다.
전역 스타일, 전역 타입, 라우터, 프로바이더(ex. redux, recoil, Tanstack Query의 Provider) 등이 여기에 정의될 수 있다.
일반적으로 슬라이드를 두지 않고 바로 세그먼트를 둔다.
현재 사용되지 않는다.
하위 레이어(entities, features, widgets)를 조합해서 완전한 기능을 제공하는 레이어로 어플리케이션의 페이지가 포함된다. 각라우터에 해당되는 페이지들을 작성하는 곳이다.
각 페이지에서 사용되는 스타일, 타입 파일도 같은 곳에 위치시킨다.(colocation)
깃 허브에서의 예시)
pages에서 사용되는 독립적인 UI 컴포넌트이다. features, entities 레이어를 조합한 레이어이다. (compositional layer)
어떻게 features, entities 묶느냐에 따라서 구성이 달라질 수 있다. 어떻게 묶어야 widgets 단위를 재사용하기 용이한지 생각해보고 구성하자.
깃 허브에서의 예시)
사용자 상호작용, 액션을 통해 비즈니스 가치를 사용자에게 전달하는 레이어(댓글 작성, 검색 등)이다. (인터렉션 관련 로직 및 비즈니스 로직 담겨있음)
특정 컴포넌트에 대한 동작 -> 실제 유저 상호작용이 일어나는 것이다.
이 레이어의 각 슬라이스는 상호작용하는 UI 요소(컴포넌트), 내부 상태 및 value를 생성하는 작업을 가능하게 하는 API 호출을 포함할 수 있다.
깃 허브에서의 예시)
비즈니스 엔티티를 나타낸다. 데이터 그 자체로 보면 된다. (ex. 유저, 상품, 주문)
깃 허브에서의 예시)

특정 비즈니스 로직에 종속되지 않은 재사용 가능한 컴포넌트와 유틸리티가 포함된다.
각 도메인이라고 생각하자.
각 Layer에는 어플리케이션 분해의 두 번째 수준인 Slice라는 하위 디렉토리가 존재한다.
슬라이스의 주요 목표는 코드를 값별로 그룹화하는 것이다.
슬라이스의 이름은 프로젝트의 비즈니스 영역에 따라 직접 결정되기 때문에 표준화되어 있지 않다. 비즈니스 도메인으로 슬라이스 이름을 정할 수 있다.
예시)
photo gallery를 생각하면 photo, album, gallery 같이 슬라이스를 만들 수 있을 것이다.
app과 shared 레이어에서는 slice를 두지 않는다.
각 슬라이스는 세그먼트로 구성된다.
팀의 합의에 따라 세그먼드의 구성과 이름이 변경될 수 있다.
일반적으로 사용되는 세그먼트들은 다음과 같다.
api와 config는 shared 레이어에만 두는 것을 권장한다고 한다.(참고)
예시를 한국어로 번역한 내용| Layer | ui | model | lib | api |
|---|---|---|---|---|
| Shared | UI 키트 | 보통 사용되지 않음 | 여러 관련 파일의 유틸리티 모듈. 개별 헬퍼를 사용해야 하는 경우 lodash-es와 같은 유틸리티 라이브러리를 고려하세요. | 인증 또는 캐싱과 같은 추가 기능이 있는 기본 API 클라이언트. |
| entities | 상호작용 요소를 위한 슬롯이 있는 비즈니스 엔티티의 골격 | 이 엔티티의 인스턴스 데이터 저장 및 해당 데이터를 조작하는 함수. 이 세그먼트는 서버 측 데이터 저장에 가장 적합합니다. TanStack Query 또는 다른 암시적 저장 방법을 사용하는 경우 이 세그먼트를 생략할 수 있습니다. | 저장과 관련되지 않은 이 엔티티 인스턴스를 조작하는 함수 | 백엔드와의 쉬운 통신을 위해 공통 API 클라이언트를 사용하는 API 메서드. |
| features | 사용자가 이 기능을 사용할 수 있게 하는 상호작용 요소 | 비즈니스 로직 및 필요에 따라 인프라 데이터 저장소(예: 현재 앱 테마). 사용자를 위해 실제 가치를 생성하는 코드. | 모델 세그먼트에서 비즈니스 로직을 간결하게 설명하는 데 도움이 되는 인프라 코드 | 백엔드에서 이 기능을 나타내는 API 메서드. 엔티티의 API 메서드를 조합할 수 있습니다. |
| widgets | 엔티티와 기능을 독립적인 UI 블록으로 구성. 에러 경계 및 로딩 상태를 포함할 수 있습니다. | 필요에 따라 인프라 데이터 저장소 | 페이지에서 블록이 기능하기 위해 필요한 비즈니스와 관련 없는 상호작용(예: 제스처) 및 기타 필요한 코드 | 보통 사용되지 않지만, 중첩 라우팅 컨텍스트(예: Remix)에서 데이터 로더를 포함할 수 있습니다. |
| pages | 엔티티, 기능 및 위젯을 완전한 페이지로 구성. 에러 경계 및 로딩 상태를 포함할 수 있습니다. | 보통 사용되지 않음 | 완전한 사용자 경험을 제공하기 위해 페이지가 필요한 비즈니스와 관련 없는 상호작용(예: 제스처) 및 기타 필요한 코드 | SSR 지향 프레임워크를 위한 데이터 로더. |
제로초님 영상에서 본 예시이다.
///예시
<Shared.Button
onClick={forkFeature.api.fork}
icon={shared.icon.fork}
data={forkEntity.model.forkCount}
/>
총정리(by 제로초님)
- app (전역 설정 / Provider, Router, Client같은 HOC가 slice가 됨)
- pages (주소별 페이지 / 각각의 주소별 페이지가 slice)
- widgets (feature의 묶음 / 어떻게 묶을지는 재사용 여부에 따라)
- features (행동 / 동사가 slice, api segment에서는 해당 행동을 요청함)
- entities (데이터 / 데이터가 slice, api segment에서는 데이터를 조회)
- shared (공유 컴포넌트 / slice 없음)
각 슬라이스와 세그먼트에는 공개 API가 있다. 공개 API는 index.js 또는 index.ts 파일이며, 이 파일을 통해 슬라이스 또는 세그먼트에서 필요한 기능만 외부로 추출하고 불필요한 기능은 격리할 수 있다. 인덱스 파일은 진입점 역할을 한다.
공개 API에 대한 규칙은 다음과 같다.
공개 API를 통해 객체지향프로그래밍의 캡슐화를 달성할 수 있다.
객체 지향 프로그래밍의 특징 4가지인 다형성, 캡슐화, 상속, 추상화를 FSD(기능 분할 설계)를 통해 어플리케이션에 적용할 수 있다.
레이어를 통해 추상화, 다형성을 달성할 수 있다.
낮은 레이어의 더 추상화 되어 있어 재사용성이 높고 특정 매개변수, 속성에 따라 컴포넌트, 기능이 다르게 작동할 수 있다.
상속 또한 레이어를 통해 달성된다. 더 높은 레이어에서 낮은 레이어를 재사용한다.
캡슐화는 슬라이스와 세그먼트의 기능 외부에서 필요하지 않은 것을 격리시키는 공개 API를 통해 달성된다. (공개 API를 통해서만 슬라이스, 세그먼트의 기능, 검포넌트에 접근 가능)
기존 코드베이스를 FSD로 마이그레이션하는 제안된 전략:
앱과 공유 계층을 개요화하여 기초를 만든다. 보통 이 계층들은 가장 작다.
모든 기존 UI를 widgets과 pages 레이어로 분배한다. FSD 규칙을 위반하는 의존성이 있더라도 그렇게 한다.
점진적으로 분해의 정밀도를 높이기 위해 features과 entities를 분리하여 pages와 widgets을 논리를 담고 있는 계층에서 순수한 조합 계층으로 전환한다.
-> features과 entities를 통해서 pages, widgets에서는 로직을 제거하고 순수하게 조합을 하여 구성되는 레이어로 구성
리팩토링하거나 프로젝트의 특정 부분을 리팩토링할 때 새로운 큰 엔터티를 추가하는 것을 자제하는 것이 좋다.
App 라우터 기준으로 살펴보자. (참고)
├── app # NextJS app folder
├── src
│ ├── app # FSD app folder
│ ├── entities
│ ├── features
│ ├── pages
│ ├── shared
│ ├── widgets
위와 같이 FSD 아키텍처를 적용한 레이어를 src/ 내에서 생성하는 것으로 가이드하고 있다.
참고
https://medium.com/@junep/fsd-feature-sliced-design%EC%97%90-%EB%8C%80%ED%95%B4-11a7b88d5c9e