// 현재 프로젝트의 폴더 구조
└─src
├─api
├─assets
├─components
├─constants
├─hooks
├─lib
├─mocks
├─pages
├─provider
├─store
├─utils
└─types
현재 프로젝트의 폴더 구조는 Monolith 구조로, 모든 코드와 기능이 기술적 요소별로 한곳에 모여있다. Monolith 구조는 기능적 역할보다는 코드의 기술적 속성에 중점을 두어 폴더를 구성하기 때문에, 프로젝트 초기에는 코드 탐색이 단순하고 명확하다는 장점이 있다. 그러나 프로젝트 규모가 점차 커질수록 의존성 관리, 확장성, 협업 효율성 등의 문제가 발생하며 Monolith 구조의 한계가 명확해진다.
서로 다른 폴더의 코드들이 서로를 참조하는 과정에서 순환 참조 문제가 발생할 수 있다. 이는 예기치 못한 동작을 유발하거나 디버깅을 어렵게 만들어 각 기능이 명확히 분리되지 않는 상황에서 특정 기능과 관련된 코드를 추적하고 의존 관계를 파악하는 데 많은 시간이 소요된다.
Monolith 구조에서는 코드가 기술적 요소로만 구분되어 있어 기능별로 캡슐화되지 않는다. 따라서 새로운 기능을 추가하거나 기존 기능을 수정할 때 예상치 못한 의존성 문제가 발생할 수 있다. 결과적으로 프로젝트가 커질수록 복잡도가 증가하고 유지보수가 어려워진다.
기술 기반으로 분류된 폴더에서는 개발자들이 같은 폴더 내에서 작업해야 하는 경우가 많아지면서 기능 간 충돌이 발생할 가능성이 커진다. PR을 통해 코드 리뷰를 해서 이러한 문제를 막을 수 있지만, 코드 리뷰가 길어지는 시간 또한 리소스 낭비이다.
서로 다른 개발자가 특정 기능을 구현할 때 일관된 기준 없이 폴더와 파일을 생성하다 보면, 구조의 일관성이 깨지기 쉬워진다. 예를 들어 한 개발자는 components 폴더에 모든 컴포넌트를 넣는 반면, 다른 개발자는 페이지에 종속된 컴포넌트를 따로 관리하고 싶어할 수도 있기 때문에 협업 시 불필요한 코드 중복과 혼란이 발생할 수 있다.
기술 기반으로 분류된 폴더에서는 폴더 구조를 변경하는 작업이 코드 전체에 영향을 미치기 쉽다. 특히, 각 폴더에 분산된 코드가 서로 얽혀 있다면 폴더 구조를 바꾸는 작업은 순환 참조나 의존성 문제를 더 심화시킬 수 있다.
기술적인 기준만을 고려한 구조에서는 기능별로 코드가 분리되지 않아 재사용성이 떨어지고 응집력 또한 낮아진다. 이로 인해 코드의 유지보수가 어려워지고, 기능 확장 시에도 새로운 폴더나 파일을 만들면서 계속해서 복잡해진다.
Monolith 구조는 간단한 프로젝트에서는 빠르게 사용할 수 있지만, 프로젝트가 커질수록 유지보수, 협업, 확장성 등 다양한 측면에서 한계를 드러내는 구조다. 이를 해결하기 위해 각 기능 단위로 코드를 분리하고 의존 관계를 명확히 관리할 수 있는 FSD(Feature Sliced Design)와 같은 구조가 점점 더 필요한 이유가 된다.
FSD(Feature Sliced Design)는 애플리케이션의 기능을 중심으로 코드를 구조화하는 설계 패턴이다. 여기서 기능 중심이란, 코드를 기술적인 요소로 분류하는 대신 비즈니스 로직에 따라 각 기능별로 독립된 모듈로 나눈다는 의미이다. 이러한 기능 단위의 모듈화를 통해 각 기능이 독립적으로 개발, 테스트, 유지보수될 수 있도록 만든다. FSD는 코드가 확장성과 유지보수성이 뛰어나도록 설계되어 있어, 개발 팀이 협업하는 과정에서 효율성을 크게 증진시켜준다.
FSD의 주요 목적은 코드를 기능 단위로 모듈화하여, 확장성과 유지보수성을 높이는 것이다. 기능 중심의 코드 구조는 팀 단위로 작업할 때 특정 기능의 코드에만 집중할 수 있게 하며, 기능별로 코드가 모듈화되어 있어 변경사항이 다른 기능에 미치는 영향을 최소화할 수 있다. 이를 통해 코드의 가독성을 높이고, 복잡성을 줄여 효율적인 협업을 가능하게 해준다.
기능 중심 설계
애플리케이션을 기능 단위로 구분하여 설계한다. 이는 각 기능이 독립적인 모듈로 구분되어, 개발과 테스트, 유지보수가 기능별로 독립적으로 이루어질 수 있게 하는 방식이다.
계층화
코드를 여러 계층으로 나누어 관심사를 분리한다. 예를 들어, UI 계층과 비즈니스 로직 계층을 구분하여 코드 간섭을 줄이고, 특정 계층만 수정해도 다른 계층에 영향을 주지 않도록 한다. 이를 통해 코드를 탐색하거나 유지보수할 때 필요하지 않은 코드까지 볼 필요가 없어져 코드의 일관성을 유지할 수 있다.
단방향 의존성
상위 계층은 하위 계층에만 의존할 수 있도록 제한한다. 이는 특정 기능이 상위 계층으로 올라가면서도 하위 계층의 기능을 참조하는 구조를 막아주어, 의존 관계가 복잡하게 얽히지 않도록 한다.
공개 API
각 모듈이 명확한 공개 API를 통해 다른 모듈과 상호작용하게 한다. 각 모듈이 제공하는 기능은 공개 API를 통해서만 접근할 수 있고, 그 외의 세부 구현은 외부로 노출되지 않아 캡슐화가 잘 유지된다. 이로 인해 코드 수정이 필요한 경우 외부에 미치는 영향을 최소화할 수 있다.
레이어는 FSD의 가장 상위 수준의 구조 6개로 제한되어 있으며 표준화되어 있다.
상위 레벨에 있는 레이어는 하위 레벨을 의존성으로 가질 수 있지만 그 반대는 성립될 수 없고, 하위 레이어로 갈수록 추상화가 심화되며 상위 레이어로 갈수록 비즈니스 로직이 심화된다.
애플리케이션 초기화 및 글로벌 설정을 정의한다. 애플리케이션의 진입점, 전역 상태 관리, 라우팅 설정, 전역 스타일 파일이 있다.
슬라이스 없이 세그먼트로 나눈다.
각 라우트에 해당하는 페이지 컴포넌트를 정의한다.
독립적으로 작동하는 대규모 기능 또는 UI 컴포넌트, 보통 하나의 완전한 기능을 정의한다.
즉 UI와 로직이 결합된 독립적인 기능 단위로 페이지의 특정 기능을 담당한다.
사용자의 특정 상호작용과 비즈니스 로직을 정의한다. 특정 작업과 관련된 로직과 UI 요소를 포함하며, 사용자 시나리오에 따른 기능을 정의한다.
애플리케이션 전반에서 반복적으로 사용되는 핵심 비즈니스 데이터 모델 및 도메인 객체를 정의한다.
예를 들어 데이터의 타입 정의와 기본적인 데이터 구조, 데이터에 대한 기본적인 연산이나 로직 관련된 유틸리티 함수나 상수가 있다.
비즈니스 로직에 의존하지 않으면서 애플리케이션 전반에서 사용되는 공통 유틸리티 및 UI 컴포넌트를 정의한다.
슬라이스 없이 세그먼트로 나눈다.
슬라이스는 각 레이어 내에서 특정 기능 영역이나 도메인을 나타내는 하위 디렉토리이다.
세그먼트는 슬라이스 내부의 코드를 목적에 따라 더 세분화한 것이다.
세그먼트의 이름은 ui, model, lib, api등이 될 수 있으며 예시는 아래와 같다.
Public API는 각 슬라이스나 세그먼트가 외부에 노출하는 인터페이스이다.
index.js 또는 index.ts 파일이며, 이 파일을 통해 슬라이스 또는 세그먼트에서 필요한 기능만 외부로 추출하고 불필요한 기능은 격리할 수 있다.
확장성
모듈성
테스트 용이성
팀 협업 효율성
코드 탐색과 유지보수의 편리함
의존성 관리
리팩토링 비용
오버엔지니어링 위험
Public API 관리
협업 과정에서의 온보딩 시간
FSD 공식 문서
(번역) 기능 분할 설계 - 최고의 프런트엔드 아키텍처
feature-sliced/examples
기능 분할 설계(FSD)를 이용한 FE 아키텍처 구성
FSD 아키텍처 알아보기
기능 분할 설계(Feature-Sliced Design, FSD)
Feature-Sliced Design을 직접 사용하면서 느낀 장점과 단점
프론트엔드 개발자 관점으로 바라보는 관심사의 분리와 좋은 폴더 구조 (feat. FSD)