[FE] FSD(Feature-Sliced Design) 폴더 구조

JunSeok·2024년 11월 2일
0

지식 기록

목록 보기
12/14

기존 폴더 구조

// 현재 프로젝트의 폴더 구조
└─src
    ├─api
    ├─assets
    ├─components
    ├─constants
    ├─hooks
    ├─lib
    ├─mocks
    ├─pages
    ├─provider
    ├─store
    ├─utils
    └─types

현재 프로젝트의 폴더 구조는 Monolith 구조로, 모든 코드와 기능이 기술적 요소별로 한곳에 모여있다. Monolith 구조는 기능적 역할보다는 코드의 기술적 속성에 중점을 두어 폴더를 구성하기 때문에, 프로젝트 초기에는 코드 탐색이 단순하고 명확하다는 장점이 있다. 그러나 프로젝트 규모가 점차 커질수록 의존성 관리, 확장성, 협업 효율성 등의 문제가 발생하며 Monolith 구조의 한계가 명확해진다.

Monolith 구조의 문제점

의존성 문제

서로 다른 폴더의 코드들이 서로를 참조하는 과정에서 순환 참조 문제가 발생할 수 있다. 이는 예기치 못한 동작을 유발하거나 디버깅을 어렵게 만들어 각 기능이 명확히 분리되지 않는 상황에서 특정 기능과 관련된 코드를 추적하고 의존 관계를 파악하는 데 많은 시간이 소요된다.

확장성 문제

Monolith 구조에서는 코드가 기술적 요소로만 구분되어 있어 기능별로 캡슐화되지 않는다. 따라서 새로운 기능을 추가하거나 기존 기능을 수정할 때 예상치 못한 의존성 문제가 발생할 수 있다. 결과적으로 프로젝트가 커질수록 복잡도가 증가하고 유지보수가 어려워진다.

협업 비효율성

기술 기반으로 분류된 폴더에서는 개발자들이 같은 폴더 내에서 작업해야 하는 경우가 많아지면서 기능 간 충돌이 발생할 가능성이 커진다. PR을 통해 코드 리뷰를 해서 이러한 문제를 막을 수 있지만, 코드 리뷰가 길어지는 시간 또한 리소스 낭비이다.
서로 다른 개발자가 특정 기능을 구현할 때 일관된 기준 없이 폴더와 파일을 생성하다 보면, 구조의 일관성이 깨지기 쉬워진다. 예를 들어 한 개발자는 components 폴더에 모든 컴포넌트를 넣는 반면, 다른 개발자는 페이지에 종속된 컴포넌트를 따로 관리하고 싶어할 수도 있기 때문에 협업 시 불필요한 코드 중복과 혼란이 발생할 수 있다.

유지 보수의 어려움

기술 기반으로 분류된 폴더에서는 폴더 구조를 변경하는 작업이 코드 전체에 영향을 미치기 쉽다. 특히, 각 폴더에 분산된 코드가 서로 얽혀 있다면 폴더 구조를 바꾸는 작업은 순환 참조나 의존성 문제를 더 심화시킬 수 있다.
기술적인 기준만을 고려한 구조에서는 기능별로 코드가 분리되지 않아 재사용성이 떨어지고 응집력 또한 낮아진다. 이로 인해 코드의 유지보수가 어려워지고, 기능 확장 시에도 새로운 폴더나 파일을 만들면서 계속해서 복잡해진다.

정리

Monolith 구조는 간단한 프로젝트에서는 빠르게 사용할 수 있지만, 프로젝트가 커질수록 유지보수, 협업, 확장성 등 다양한 측면에서 한계를 드러내는 구조다. 이를 해결하기 위해 각 기능 단위로 코드를 분리하고 의존 관계를 명확히 관리할 수 있는 FSD(Feature Sliced Design)와 같은 구조가 점점 더 필요한 이유가 된다.

FSD(Feature Sliced Design) 소개

FSD(Feature Sliced Design)는 애플리케이션의 기능을 중심으로 코드를 구조화하는 설계 패턴이다. 여기서 기능 중심이란, 코드를 기술적인 요소로 분류하는 대신 비즈니스 로직에 따라 각 기능별로 독립된 모듈로 나눈다는 의미이다. 이러한 기능 단위의 모듈화를 통해 각 기능이 독립적으로 개발, 테스트, 유지보수될 수 있도록 만든다. FSD는 코드가 확장성과 유지보수성이 뛰어나도록 설계되어 있어, 개발 팀이 협업하는 과정에서 효율성을 크게 증진시켜준다.

주요 목적

FSD의 주요 목적은 코드를 기능 단위로 모듈화하여, 확장성과 유지보수성을 높이는 것이다. 기능 중심의 코드 구조는 팀 단위로 작업할 때 특정 기능의 코드에만 집중할 수 있게 하며, 기능별로 코드가 모듈화되어 있어 변경사항이 다른 기능에 미치는 영향을 최소화할 수 있다. 이를 통해 코드의 가독성을 높이고, 복잡성을 줄여 효율적인 협업을 가능하게 해준다.

FSD의 핵심 원칙

  1. 기능 중심 설계
    애플리케이션을 기능 단위로 구분하여 설계한다. 이는 각 기능이 독립적인 모듈로 구분되어, 개발과 테스트, 유지보수가 기능별로 독립적으로 이루어질 수 있게 하는 방식이다.

  2. 계층화
    코드를 여러 계층으로 나누어 관심사를 분리한다. 예를 들어, UI 계층과 비즈니스 로직 계층을 구분하여 코드 간섭을 줄이고, 특정 계층만 수정해도 다른 계층에 영향을 주지 않도록 한다. 이를 통해 코드를 탐색하거나 유지보수할 때 필요하지 않은 코드까지 볼 필요가 없어져 코드의 일관성을 유지할 수 있다.

  3. 단방향 의존성
    상위 계층은 하위 계층에만 의존할 수 있도록 제한한다. 이는 특정 기능이 상위 계층으로 올라가면서도 하위 계층의 기능을 참조하는 구조를 막아주어, 의존 관계가 복잡하게 얽히지 않도록 한다.

  4. 공개 API
    각 모듈이 명확한 공개 API를 통해 다른 모듈과 상호작용하게 한다. 각 모듈이 제공하는 기능은 공개 API를 통해서만 접근할 수 있고, 그 외의 세부 구현은 외부로 노출되지 않아 캡슐화가 잘 유지된다. 이로 인해 코드 수정이 필요한 경우 외부에 미치는 영향을 최소화할 수 있다.

FSD의 구성 요소

레이어

레이어는 FSD의 가장 상위 수준의 구조 6개로 제한되어 있으며 표준화되어 있다.
상위 레벨에 있는 레이어는 하위 레벨을 의존성으로 가질 수 있지만 그 반대는 성립될 수 없고, 하위 레이어로 갈수록 추상화가 심화되며 상위 레이어로 갈수록 비즈니스 로직이 심화된다.

app

애플리케이션 초기화 및 글로벌 설정을 정의한다. 애플리케이션의 진입점, 전역 상태 관리, 라우팅 설정, 전역 스타일 파일이 있다.
슬라이스 없이 세그먼트로 나눈다.

pages

각 라우트에 해당하는 페이지 컴포넌트를 정의한다.

widgets(선택적 레이어)

독립적으로 작동하는 대규모 기능 또는 UI 컴포넌트, 보통 하나의 완전한 기능을 정의한다.
즉 UI와 로직이 결합된 독립적인 기능 단위로 페이지의 특정 기능을 담당한다.

features

사용자의 특정 상호작용과 비즈니스 로직을 정의한다. 특정 작업과 관련된 로직과 UI 요소를 포함하며, 사용자 시나리오에 따른 기능을 정의한다.

entities(선택적 레이어)

애플리케이션 전반에서 반복적으로 사용되는 핵심 비즈니스 데이터 모델 및 도메인 객체를 정의한다.
예를 들어 데이터의 타입 정의와 기본적인 데이터 구조, 데이터에 대한 기본적인 연산이나 로직 관련된 유틸리티 함수나 상수가 있다.

shared

비즈니스 로직에 의존하지 않으면서 애플리케이션 전반에서 사용되는 공통 유틸리티 및 UI 컴포넌트를 정의한다.
슬라이스 없이 세그먼트로 나눈다.

슬라이스

슬라이스는 각 레이어 내에서 특정 기능 영역이나 도메인을 나타내는 하위 디렉토리이다.

목표

  • 슬라이스를 통해 특정 비즈니스 도메인이나 기능을 캡슐화한다.
  • 독립적으로 개발 및 테스트를 한다.
  • 다른 슬라이스와의 의존성을 명시적으로 관리한다.
  • 레이어에서 슬라이스끼리 기능을 공유하지 않고 독립적으로 사용한다.

세그먼트

세그먼트는 슬라이스 내부의 코드를 목적에 따라 더 세분화한 것이다.
세그먼트의 이름은 ui, model, lib, api등이 될 수 있으며 예시는 아래와 같다.

  • api - 외부 서비스와의 통신.
  • UI - UI 컴포넌트.
  • model - 비즈니스 로직,상태와의 상호 작용, actions 및 selectors 등.
  • lib - 슬라이스 내에서 사용되는 보조 기능, 유틸리티 함수, 헬퍼 함수, 훅.
  • config - 설정 및 상수

Public API

Public API는 각 슬라이스나 세그먼트가 외부에 노출하는 인터페이스이다.
index.js 또는 index.ts 파일이며, 이 파일을 통해 슬라이스 또는 세그먼트에서 필요한 기능만 외부로 추출하고 불필요한 기능은 격리할 수 있다.

목표

  • 캡슐화: Public API는 폴더 외부로 필요한 것만 노출하도록 하여 내부 구조를 감추는 역할을 한다. 만약 모든 파일을 외부에서 직접 import하도록 허용하면, 해당 feature의 구조가 외부에 그대로 노출되어 캡슐화가 깨진다.
  • 일관성 유지: 여러 팀원이 작업할 때, Public API를 통한 import 경로가 있으면 모든 코드에서 동일한 경로를 사용하게 되어 일관성이 유지된다.
  • 유지보수성: Public API를 통해 노출된 파일만 공식 API로 간주하고, 내부 파일의 구조가 변경되더라도 외부에서는 이를 알 필요가 없기 때문에 유지보수가 용이하다.
  • 간결한 import 경로: Public API 덕분에 import 경로가 간결해지며, 특정 feature의 모든 export를 한 경로로 모을 수 있다.

장점

  1. 확장성

    • 새로운 기능을 추가하거나 기존 기능을 수정하기에 용이하다. 기능이 명확하게 분리되어 있어 코드베이스가 커지더라도 각 기능을 독립적으로 다룰 수 있으며, 전체 코드에 영향을 주지 않으면서 특정 기능을 확장할 수 있기 때문이다.
  2. 모듈성

    • 각 기능이 독립적인 모듈로 구성되어 있어 재사용성과 유지보수성이 높아진다. 다른 기능에 종속되지 않고 독립적으로 동작하기 때문에 기능 단위로 쉽게 유지보수할 수 있는 구조이다.
  3. 테스트 용이성

    • 기능별로 코드가 분리되어 있어 각 기능을 독립적으로 테스트하기가 용이하다. 슬라이스와 세그먼트 단위로 테스트가 가능해 에러 발생 시 디버깅이 수월하다는 장점이 있다.
  4. 팀 협업 효율성

    • 기능 단위로 작업을 분배하여 병렬로 개발할 수 있다. 각 개발자가 독립된 기능을 담당하므로 작업 간섭이 줄어들고, 협업 시 코드 충돌 가능성도 낮아진다.
  5. 코드 탐색과 유지보수의 편리함

    • 일관된 구조 덕분에 코드 탐색이 쉽다. 프로젝트 내에서 필요한 코드의 위치를 빠르게 파악할 수 있어 유지보수가 수월하고, 새로운 팀원이 합류해도 구조를 이해하기가 용이하다.
  6. 의존성 관리

    • 명시적으로 의존성을 관리하여 코드의 예측 가능성이 높아진다. 단방향 의존성을 유지해 구조가 명확해지므로 코드 복잡성을 줄일 수 있다.

단점

  1. 리팩토링 비용

    • 처음부터 FSD를 사용하지 않았다면, 마이그레이션 과정에서 큰 리팩토링 비용이 발생할 수 있다. 특정 코드를 어디에 배치해야 할지 고민해야 하는 등 초기 생산성이 낮아질 가능성도 크다.
  2. 오버엔지니어링 위험

    • 작은 프로젝트에서는 FSD 구조가 오히려 복잡성을 높일 수 있다. 작은 프로젝트에서 기능 중심 구조는 단순한 구조보다 효율이 떨어질 수 있어 불필요하게 복잡해질 수 있다.
  3. Public API 관리

    • Public API를 설정해 각 모듈에 접근할 때 진입점을 정의하지만, 파일 경로나 폴더 구조가 변경되면 Public API 파일도 수정해야 해 추가적인 리팩토링 작업이 발생할 수 있다.
  4. 협업 과정에서의 온보딩 시간

    • FSD 규칙을 프로젝트 전반에 일관되게 적용해야 하므로 팀원 온보딩에 시간이 소요될 수 있다. 모든 팀원이 규칙에 따라 코드를 작성해야 해 협업 초기에는 일관성 유지를 위해 학습 곡선이 존재한다.

출처

FSD 공식 문서
(번역) 기능 분할 설계 - 최고의 프런트엔드 아키텍처
feature-sliced/examples
기능 분할 설계(FSD)를 이용한 FE 아키텍처 구성
FSD 아키텍처 알아보기
기능 분할 설계(Feature-Sliced Design, FSD)
Feature-Sliced Design을 직접 사용하면서 느낀 장점과 단점
프론트엔드 개발자 관점으로 바라보는 관심사의 분리와 좋은 폴더 구조 (feat. FSD)

profile
최선을 다한다는 것은 할 수 있는 한 가장 핵심을 향한다는 것

0개의 댓글