FSD 아키텍처 살펴보기

이희제·2024년 6월 11일
post-thumbnail

개발을 진행하면서 좋은 구조로 어떻게 짤 수 있을까가 항상 고민이다.

그 와중에 FSD 아키텍쳐에 대해 알게 되었고 해당 아키텍쳐에 대해 알아보고자 한다.

추가로 Next.js에서 어떻게 적용할 수 있을지 확인해보자.


FSD는 Feature-Sliced Design의 약자로서 기능 분할 설계를 의미한다. 애플리케이션을 기능 단위로 조직화하여 관리하는 아키텍쳐이다.

기능 단위로 분리하면 개발 및 유지보수를 용이하게 한다.

점진적으로 소프트웨어 규모가 커질 예정이라면 FSD를 적용할 수 있을 것으로 예상된다.

해당 아키텍쳐는 다음과 같은 3가지의 개념으로 나눠져있다.

1. Layers
2. Slices
3. Segments

각각 살펴보자.

레이어 (Layer)

Layer는 최상위 디렉토리이다. 최대 7개로 구성되어 있는데 processes는 더 이상 사용되지 않는다고 한다. 그리고 각 디렉토리 별로 옵셔널하게 사용할 수 있다.

상위 레벨에 있는 레이어는 하위 레벨을 의존성으로 가질 수 있지만 그 반대는 성립될 수 없다.

하위 레이어로 갈수록 추상화가 심화되며 상위 레이어로 갈수록 비즈니스 로직이 심화된다.

따라서 계층이 낮을수록 추상화 수준이 높아지기 때문에 재상용성이 높아지고 자율성이 낮아진다.

app

어플리케이션 로직이 초기화되는 곳이다.

전역 스타일, 전역 타입, 라우터, 프로바이더(ex. redux, recoil, Tanstack Query의 Provider) 등이 여기에 정의될 수 있다.

일반적으로 슬라이드를 두지 않고 바로 세그먼트를 둔다.

process (Deprecated)

현재 사용되지 않는다.

pages

하위 레이어(entities, features, widgets)를 조합해서 완전한 기능을 제공하는 레이어로 어플리케이션의 페이지가 포함된다. 각라우터에 해당되는 페이지들을 작성하는 곳이다.

각 페이지에서 사용되는 스타일, 타입 파일도 같은 곳에 위치시킨다.(colocation)

깃 허브에서의 예시)

  • Repository Page
  • User's repositories
  • Branches in a repositories

widgets

pages에서 사용되는 독립적인 UI 컴포넌트이다. features, entities 레이어를 조합한 레이어이다. (compositional layer)

어떻게 features, entities 묶느냐에 따라서 구성이 달라질 수 있다. 어떻게 묶어야 widgets 단위를 재사용하기 용이한지 생각해보고 구성하자.

깃 허브에서의 예시)

  • List of files in a repository
  • Comment in thread (코멘트 영역)
  • Repository card

features (optional)

사용자 상호작용, 액션을 통해 비즈니스 가치를 사용자에게 전달하는 레이어(댓글 작성, 검색 등)이다. (인터렉션 관련 로직 및 비즈니스 로직 담겨있음)

특정 컴포넌트에 대한 동작 -> 실제 유저 상호작용이 일어나는 것이다.

이 레이어의 각 슬라이스는 상호작용하는 UI 요소(컴포넌트), 내부 상태 및 value를 생성하는 작업을 가능하게 하는 API 호출을 포함할 수 있다.

깃 허브에서의 예시)

  • Edit a file
  • Leave a comment
  • Merge branches

entities (optional)

비즈니스 엔티티를 나타낸다. 데이터 그 자체로 보면 된다. (ex. 유저, 상품, 주문)

깃 허브에서의 예시)

  • Repository
  • File
  • Commit

shared

특정 비즈니스 로직에 종속되지 않은 재사용 가능한 컴포넌트와 유틸리티가 포함된다.


슬라이스 (Slice)

각 도메인이라고 생각하자.

각 Layer에는 어플리케이션 분해의 두 번째 수준인 Slice라는 하위 디렉토리가 존재한다.

슬라이스의 주요 목표는 코드를 값별로 그룹화하는 것이다.

슬라이스의 이름은 프로젝트의 비즈니스 영역에 따라 직접 결정되기 때문에 표준화되어 있지 않다. 비즈니스 도메인으로 슬라이스 이름을 정할 수 있다.

예시)

photo gallery를 생각하면 photo, album, gallery 같이 슬라이스를 만들 수 있을 것이다.

app과 shared 레이어에서는 slice를 두지 않는다.

세그먼트 (Segment)

각 슬라이스는 세그먼트로 구성된다.

팀의 합의에 따라 세그먼드의 구성과 이름이 변경될 수 있다.

일반적으로 사용되는 세그먼트들은 다음과 같다.

  • api: 데이터 fetch 로직, 서버 요청
  • UI: UI 구성 컴포넌트
  • model: 비즈니스 로직과 store, actions, 데이터 관련 디렉토리
    • pages와 app에서는 보통 사용되지 않는다.
  • lib: utils, hooks
  • config
  • consts: 상수

api와 config는 shared 레이어에만 두는 것을 권장한다고 한다.(참고)

공식 문서에서의 예시

예시를 한국어로 번역한 내용
Layeruimodellibapi
SharedUI 키트보통 사용되지 않음여러 관련 파일의 유틸리티 모듈.
개별 헬퍼를 사용해야 하는 경우 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가 있다. 공개 API는 index.js 또는 index.ts 파일이며, 이 파일을 통해 슬라이스 또는 세그먼트에서 필요한 기능만 외부로 추출하고 불필요한 기능은 격리할 수 있다. 인덱스 파일은 진입점 역할을 한다.

공개 API에 대한 규칙은 다음과 같다.

  • 애플리케이션 슬라이스와 세그먼트는 공개 API 인덱스 파일에 정의된 슬라이스의 기능과 컴포넌트만 사용한다.
  • 공개 API에 정의되지 않은 슬라이스 또는 세그먼트의 내부 부분은 격리된 것으로 간주되며 슬라이스 또는 세그먼트 내부에서만 접근할 수 있다.

공개 API를 통해 객체지향프로그래밍의 캡슐화를 달성할 수 있다.


FSD가 달성하는 객체 지향 프로그래밍의 특징

객체 지향 프로그래밍의 특징 4가지인 다형성, 캡슐화, 상속, 추상화를 FSD(기능 분할 설계)를 통해 어플리케이션에 적용할 수 있다.

레이어를 통해 추상화, 다형성을 달성할 수 있다.

낮은 레이어의 더 추상화 되어 있어 재사용성이 높고 특정 매개변수, 속성에 따라 컴포넌트, 기능이 다르게 작동할 수 있다.

상속 또한 레이어를 통해 달성된다. 더 높은 레이어에서 낮은 레이어를 재사용한다.

캡슐화는 슬라이스와 세그먼트의 기능 외부에서 필요하지 않은 것을 격리시키는 공개 API를 통해 달성된다. (공개 API를 통해서만 슬라이스, 세그먼트의 기능, 검포넌트에 접근 가능)


공식 문서에서 제안하는 적용 전략

기존 코드베이스를 FSD로 마이그레이션하는 제안된 전략:

  1. 앱과 공유 계층을 개요화하여 기초를 만든다. 보통 이 계층들은 가장 작다.

  2. 모든 기존 UI를 widgets과 pages 레이어로 분배한다. FSD 규칙을 위반하는 의존성이 있더라도 그렇게 한다.

  3. 점진적으로 분해의 정밀도를 높이기 위해 features과 entities를 분리하여 pages와 widgets을 논리를 담고 있는 계층에서 순수한 조합 계층으로 전환한다.

-> features과 entities를 통해서 pages, widgets에서는 로직을 제거하고 순수하게 조합을 하여 구성되는 레이어로 구성

리팩토링하거나 프로젝트의 특정 부분을 리팩토링할 때 새로운 큰 엔터티를 추가하는 것을 자제하는 것이 좋다.


Next.js에서의 아키텍쳐 적용

App 라우터 기준으로 살펴보자. (참고)

├── app # NextJS app folder  
├── src  
│ ├── app # FSD app folder  
│ ├── entities  
│ ├── features  
│ ├── pages  
│ ├── shared  
│ ├── widgets

위와 같이 FSD 아키텍처를 적용한 레이어를 src/ 내에서 생성하는 것으로 가이드하고 있다.


FSD의 장단점

장점

  • 아키텍처 구성 요소를 쉽게 교체, 추가, 제거할 수 있다.
  • 아키텍처가 표준화된다.
  • 확장성이 있다.
  • 방법론은 개발 스택과 독립적이다.
  • 예기치 않은 부작용 없이 모듈 간의 연결이 제어되고 명시적이다.
  • 아키텍처 방법론이 비즈니스 지향적이다.

단점

  • 다른 많은 아키텍처 솔루션들에 비해 높은 진입 장벽이 있다.
  • 인식, 팀 문화 및 개념 준수가 필요하다.
  • 도전 과제와 문제를 나중이 아닌 즉시 해결해야 한다. 코드 문제와 개념에서 벗어난 부분을 즉시 확인할 수 있다. 그러나 이는 장점으로도 볼 수 있다.

참고
https://medium.com/@junep/fsd-feature-sliced-design%EC%97%90-%EB%8C%80%ED%95%B4-11a7b88d5c9e

profile
그냥 하자

0개의 댓글