Github
PostsreactFSD

FSD

Feature-Sliced Design

개요


효율적인 코드 구조 설계의 중요성

효율적인 코드 구조 설계는 여러 가지 이점을 제공한다. 올바른 구조를 통해 다음과 같은 혜택을 얻을 수 있다:

  • 접근성 향상: 기능과 연관된 코드를 쉽게 찾을 수 있어 개발 속도가 높아진다.
  • 빠른 판단: 어떤 로직 파일을 어디에 둘지 빠르게 결정할 수 있어 코드 관리가 용이해진다.
  • 작업 효율성: 폴더 구조 및 로직 구분이 명확해져 업무 효율이 증대된다.

기존 폴더 구조의 장점과 한계

현재까지는 pages와 같은 상위 레벨에 도메인별 폴더를 두고, 그 아래에 shared, apis, components, types, hooks 등으로 기능을 구분하는 구조를 사용했다. 이러한 구조의 장점은 다음과 같다:

  • 초기 명확성: 프로젝트 시작 단계나 규모가 작을 때, 페이지별로 상위 폴더를 나누고 페이지 내부에서 사용하는 API, hook, component를 정의하는 방식이 명확하게 느껴진다.
  • 직관적인 구분: 기능별로 구분된 폴더 구조는 처음 접근하는 개발자에게도 직관적으로 이해할 수 있는 장점을 제공한다.

하지만, 프로젝트의 규모가 커지면서 몇 가지 문제점이 발생했다:

  • 복잡성 증가: 다른 도메인에서 사용하는 API나 컴포넌트를 사용할 경우가 빈번해지면서 구조가 복잡해졌다.
  • 경계 모호성: 도메인별로 구분하는 방식이 결합도를 증가시키고, 응집력을 감소시켜 무분별한 참조가 발생했다. 이는 접근성을 낮추는 결과를 초래했다.

개선 방안

효율적인 코드 구조를 위해 고려할 수 있는 개선 방안은 다음과 같다:

  • 기능 기반 구분: 도메인보다는 기능에 기반한 폴더 구조를 채택하여 결합도를 낮추고 응집력을 높인다.
  • 공통 모듈화: 여러 도메인에서 사용되는 API나 컴포넌트를 공통 모듈로 분리하여 재사용성을 높인다.
  • 명확한 경계 설정: 각 모듈의 경계를 명확히 하여 무분별한 참조를 줄이고 접근성을 향상시킨다.

이러한 개선 방안을 통해 더 효율적이고 관리하기 쉬운 코드 구조를 구축할 수 있다.

FSD는 Feature Sliced Design으로 모놀리식 폴더 구조를 가진다. 도메인이 아닌 기능으로 구분짓고 단반향으로 참조하기 때문에 현재 가지고 있는 문제를 해결해볼 수 있을 것이라 기대한다.

FSD란


프론트엔드에서 모놀리식 아키텍처 방법론이다. 계속해서 변화하는 요구사항 아래에서 프로젝트를 구조화하고 이해하기 쉽게 만드는데에 목적을 가진다.

https://feature-sliced.design/docs/get-started/overview

FSD가 가지는 컨벤션들은 아래와 같다.

  1. 폴더 구조

    프로젝트를 구성하는 폴더 구조의 컨벤션은 위 그림과 같다.

    Layer는 프로젝트에서 가장 상위 폴더로 6개로 구분한다.

    • shared는 어플리케이션 전반적으로 재사용 가능한 모듈이 들어간다.

      유틸리티 함수, 공통 스타일, 전역 상수 등이 있다.

    • entities는 가장 밑단의 비즈니스 로직이 들어간다.

      도메인 엔티티에 대한 데이터 모델, 상태 관리, API를 포함한다.

    • features는 비즈니스 밸류를 전달할 수 있는 user action을 처리하는 로직이 들어간다.

      예를 들어 디자인 시스템의 단순 Button은 아무런 비즈니스 가치가 있는 컴포넌트가 아니지만, ‘결제하기’ Button 또는 ‘좋아요’ Button은 비즈니스 가치를 가진다.

    • widgets는 feature와 entity를 조합해서 만들어지는 block이 들어간다.

      페이지 내에서 사용되는 독립적인 UI 컴포넌트 그룹을 포함한다.

    • pages는 페이지를 구성하는 로직이 들어간다.

      Next.js가 아니라면 pages layer에서는 slice를 나누는 프로젝트도 있다. 대부분 ui segement로 구분지어 사용한다.

    • app layer는 앱 전반적으로 적용되는 style이나 provider가 들어갈 수 있다.

      애플리케이션 초기화 및 주요 설정을 담당한다.

  2. 단방향

    위 그림에서 아래 단계의 Layer의 모듈만을 가져올 수 있다.

  3. 상호 참조 금지

    slices 내에서는 다른 slice를 사용하지 않는다. segment에서는 서로 참조 가능하다.

  4. strict public API

    다른 모듈에서 import할 수 있는 slice나 segment를 정의하고, 외부에서는 정의된 API만 사용한다.

    변경사항이 발생했을 시, import하는 모듈에서는 수정할 필요 없고 내부에서 자유롭게 변경하기 위함이다.

구조 예시


많은 example을 봤지만, 모두 달랐다. 명확하게 “이 코드는 여기 있어야 해” 정의해놓은 방식이 아니기 때문에 FSD 원칙을 바탕으로 자신만의 방식을 찾아보는 시간이 필요하다고 생각한다.

FSD로 작업하면서, 세운 나만의 방식은

  1. entities → wigets로 갈 수록 컴포넌트는 도메인 specific해지고, 커진다.
  2. entities에는 api, model, type, schema를 다룬다.
  3. features에는 재사용 가능한 UI 로직을 다룬다.
  4. widgets에는 페이지의 섹션 사이즈 정도의 컴포넌트를 다룬다.

“다룬다”라고 적은 이유는 해당 컴포넌트에서 사용되는 것은 같은 slice에 두기 때문에 hook이나 부가적인 작은 컴포넌트도 함께 위치하기 때문이다.

Example 1.

Entities

1- src 2 - entities 3 - <name> 4 - <name>.apis.ts 5 - <name>.queries.ts 6 - <name>.model.ts 7 - <name>.lib.ts 8 - <name>.types.ts 9 - <name>.contracts.ts
  • apis:
    • queryFn에서 사용할 rest api function
  • queries:
    • domain별 query에 대한 정보(key or query client api)
    • query hook을 래핑하여 query별 동작 작성
  • model
    • entity에 해당하는 데이터 store 관리
    • MVC에 모델과 비슷한 개념이라고 생각
  • lib
    • entity에 해당하는, 도메인에 상관있는 util 함수
  • types
    • entity 도메인에 해당하는 DTO, 제너럴한 type 정의
  • contracts
    • schema 정의
    • zod의 사용을 예시로 들 수 있음

Features

1- src 2 - features 3 - <name> 4 - <name>.ui 5 - <name>.types

도메인 가장 밑단의 컴포넌트를 구성한다. entity에 적용하지 않는 이유는 entity와 feature를 분류하는 기준이 모호할 뿐더러 디자인 시스템을 사용하고 있다면 entity에 구현될 컴포넌트의 크기가 상당히 작다고 생각하기 때문

type 시스템을 제대로 구축한다면 entity에 가장 작은 구성요소와 이것들을 조합하여 더 큰 타입을 가지게 될 것이고, entity 더 위의 layer에서 자유롭게 구성하면된다고 생각

Widgets

1- src 2 - widgets 3 - <name> 4 - <name>.ui

widget layer에는 의미있는 블럭을 놔둔다. 예를 들어 게시판의 리스트들 컴포넌트를 말할 수 있다.

해당 부분에서는 UI 로직이 많거나, 구성하는 컴포넌트가 많아질 수 있다.

따라서 .ui 파일 하나로 관리했을 때, 내부의 코드가 길어질 수 있기 때문에 UI 폴더를 만들어도 좋다고 생각. 통일을 위해서라면 entities나 features도 UI는 파일이 아닌 폴더로 만들어도 좋다고 생각

Example 2.

entities

위와 폴더 구조는 동일하나 개념을 다르게 생각해볼 수 있다. 가장 밑단의 어플리케이션 도메인에 종속적인 컴포넌트가 들어갈 수 있다. 컴포넌트의 interface는 같은 slice에 정의된 Model에 따라서 결정할 수 있다.

Feature

entities에서 정의한 컴포넌트에서 기능을 붙인다. 이 단계에서는 어플리케이션에서 동작하는, query를 요청하거나 특정 이벤트를 처리하는 핸들러가 부착될 수 있다.

Widgets

페이지에서 섹션에 비슷할 정도의 컴포넌트를 정의할 수 있다. feature 및 entity를 사용하여 페이지단의 블럭을 만든다고 생각할 수 있다.

Pages

Widgets에서 컴포넌트가 잘 작성되었다면 페이지 단에서는 간단히 widget을 조합하여 레이아웃만 잡게 된다.

FSD 적용 예시


예를 들면 구글 페이지를 예시로, 위 Example 2를 사용하면 대략 아래와 같이 구조를 잡을 수 있다.

1- src 2 - app 3 - styles 4 - providers 5 - pages 6 - main 7 - widgets 8 - footer 9 - navigation-bar 10 - features 11 - search-bar 12 - index 13 - hooks 14 - ui 15 - avatar 16 - index 17 - ui 18 - entities 19 - search 20 - index 21 - apis 22 - types 23 - user 24 - index 25 - apis

FSD 느낀점


장점

  1. 유지보수성

    도메인 단위로 그룹을 지으면, 서로 다른 도메인에서 사용되는 컴포넌트나 hook을 찾기 어려워 수정이 복잡해질 수 있다. 하지만 FSD를 사용하면 수정이 필요한 layer부터 위쪽으로만 변경 사항을 반영해 나가므로 유지 보수가 용이하다.

  2. 모듈화 및 재사용성

    FSD는 기능 단위로 코드를 분리하여 각 모듈을 독립적으로 개발하고 테스트할 수 있게 한다. 이는 코드의 재사용성을 높이고 중복 코드를 줄이는 데 도움이 된다. Slice 간의 참조를 제한하는 규칙으로 모듈화가 자연스럽게 이루어지며, 특히 entity 레이어는 query hook, model, type은 어플리케이션의 베이스가 되는 부분으로 특정 도메인이나 페이지에 종속되지 않기 때문에 어디에서든 쉽게 사용할 수 있다.

    또한, 여러 페이지에 공통으로 사용되는 컴포넌트는 feature 레이어에서 재사용성이 높다. 사이즈 조절과 인터페이스 확장을 통해 다양한 상황에 맞게 사용할 수 있다.

  3. 확장성

    코드가 기능 단위로 조직되어 있어 특정 기능의 수정이나 확장이 다른 부분에 영향을 미치지 않고 쉽게 이루어질 수 있다. 새로운 기능을 추가할 때 기존 코드를 최소한으로 수정하고 새로운 모듈을 추가하기만 하면 되므로 애플리케이션 확장이 용이하다.

단점

  1. 진입 장벽이 높다.

    Feature Sliced Architecture를 처음 도입할 때는 설정과 구조화에 많은 시간이 필요할 수 있다. 초기 설정 단계에서 기능 단위로 코드를 구조화하는 작업이 필요하며, 이는 많은 시간이 소요될 수 있다. 추가로 잘못된 구조화는 오히려 복잡성을 증가시킬 수 있다. 또한 마이그레이션 단계에서 모든 기능을 예측하고 구조화하는 것은 어려울 수 있으며, 이후 기능 추가 시 재구조화가 필요할 수 있다.

    기존의 다른 아키텍처 스타일에 익숙한 개발자들에게는 새로운 방식에 대한 학습이 필요하다. 초기 단계에서 생산성을 낮출 수 있다.

  2. 사람마다 차이가 있을 수 있다.

    여러 팀이 동시에 작업할 때, 코드 스타일이나 설계 원칙을 일관되게 유지하는 것이 어려울 수 있다. 예를 들어, router를 app 폴더에 넣을지 shared 폴더에 넣을지에 대한 의견 차이가 있을 수 있다. 이를 해결하기 위해 강력한 코드 리뷰와 규칙이 필요하다. 팀원마다 스타일이나 설계 원칙이 다를 수 있어, 프로젝트의 일관성을 유지하는 데 어려움이 있을 수 있다.

  3. 모놀리식 구조

    FSD의 장점 중 하나는 기능별로 코드를 분리하는 것이다. Layer내에서 slice가 많아지다 보면 관계있는 slice끼리 합치려는 생각이든다. 하지만 관려된 slice를 합치려고 한다면 결합도를 낮추는 FSD가 가지는 장점을 잃게 될 수 있다.

    기능별로 파일과 디렉토리를 분리하다 보면 파일과 디렉토리의 수가 급격히 증가할 수 있다. 이는 파일 탐색 및 관리를 어렵게 만들 수 있다. 과도한 분리는 오히려 복잡성을 증가시키고 유지 보수를 어렵게 만들 수 있다.