FSD Architecture와 Modular Architecture

BangDori·2024년 4월 14일
0
post-thumbnail

React를 활용하여 프로젝트를 진행하다 보면, 매번 어느 순간부터 프로젝트 내부 구조가 깊어지고 복잡해지는 상황에 마주하게 된다. 그리고 이러한 문제는 실제 개발 과정에서 가독성과 개발 생산성을 저하시키고, 추후에는 유지 보수성이 복잡해지는 문제까지도 초래한다.

해당 글에서는 프론트엔드 개발자, 특히 React 개발자가 종종 마주하는 아키텍처와 관련한 문제에 대해 얘기해보려고 한다. 그리고 이러한 문제에 마주하는 프론트엔드 개발자를 위한 하나의 해결책이 될 수 있는 FSD Architecture를 소개하려고 한다.

해당 게시물에서는 기존의 모듈식 아키텍처를 활용하여 진행했었던 나의 프로젝트를 분석하고, Yan Levin님이 작성해주신 Feature-Sliced Design: The Best Frontend Architecture를 포함해 많은 개발자분들이 작성해주신 FSD Architecture에 대한 포스팅들을 참고하여 FSD에 대해 작성해보겠다.

📌 개요

과거 기존의 단순한 모듈식 아키텍처를 활용하여 프로젝트를 진행하였을 때, 다음과 같은 형식으로 프로젝트의 폴더 구조가 구성되었었습니다.

위 이미지는 작년에 1년동안 모든 열과 성을 다한 캡스톤 프로젝트의 폴더 구조입니다. 초기까지만 해도 분명, 모듈식 아키텍처의 문제점에 크게 인식하지 못하고 있었습니다. 하지만 프로젝트의 깊이가 깊어질수록, 파일의 수가 많아질수록 어떤 파일이 어디에 위치했는지를 찾아나가는 데 큰 어려움이 있었습니다.

특히 코드를 리팩토링해나가는 과정에서 문제점을 크게 느끼기 시작하였습니다.

물론 모듈 자체를 하나의 폴더로 묶는다는 아이디어는 그 의미가 주는 통일성이 있었고, 단순한 특징을 가지고 있어 빠르게 적용할 수 있었습니다. 하지만 모듈들이 어떻게 연결되어 있는지 전체적인 구조를 그렸을 때, 문제점이 굉장히 복잡하게 연결되어 있는 것을 확인할 수 있었습니다.

이러한 프로젝트 구성은 한 곳에서 에러가 발생하게 된다면, 에러가 발생한 원인을 분석하기 힘들어지게 됩니다. 실제로 프로젝트를 진행할 때 에러가 발생했을 때, 이를 해결하기 위해 여기저기를 돌아다니면서 어려움을 겪었습니다. 그렇다면 오늘 포스팅하려는 FSD Architecture는 무엇이며, 어떠한 구조를 가지고 있을까요?

📌 FSD Architecture

공식 홈페이지에서는 아래와 같이 FSD Architecture에 대해 설명하고 있다.

Feature-Sliced Design (FSD) is an architectural methodology for scaffolding front-end applications. Simply put, it's a compilation of rules and conventions on organizing code. The main purpose of this methodology is to make the project more understandable and structured in the face of ever-changing business requirements.

FSD(Feature-Sliced Design)는 프론트엔드 애플리케이션을 스캐폴딩하기 위한 아키텍처 방법론입니다. 간단히 말해, 코드 구성에 대한 규칙과 규칙을 모아놓은 것입니다. 이 방법론의 주요 목적은 계속해서 변화하는 비즈니스 요구 사항에 직면하여 프로젝트를 보다 이해하기 쉽고, 구조화하는 것입니다.

그렇다면 FSD는 어떠한 방법을 사용하여, 프로젝트를 보다 이해하기 쉽게 구조화하고 있을까?

📚 FSD의 개념

FSD Architecture에는 세 가지의 개념이 존재합니다.

  1. 레이어 (Layer)
  2. 슬라이스 (Slice)
  3. 세그먼트 (Segment)

FSD에서 프로젝트는 레이어로 구성되며, 각 레이어는 슬라이스로 구성되고, 각 슬라이스는 세그먼트로 구성됩니다.

우선 레이어부터 슬라이스, 세그먼트에 대해 알아보겠습니다.

📗 Layer

레이어는 프로젝트에 src 하위에 위치하는 최상위 디렉토리이자 애플리케이션의 첫 번째 단계입니다. 레이어는 최대 7개로, 일부는 선택 사항이지만 표준화되어 수직으로 위치합니다. 한 레이어의 모듈은 엄격하게 아래 레이어의 모듈과만 상호 작용할 수 있습니다.

각 레이어가 하는 역할은 다음과 같습니다.

  1. app: 애플리케이션 로직이 초기화되는 곳으로 (provider, router, root style 등)이 정의되는 레이어
  2. processes (deprecated): 여러 페이지에 걸쳐 있는 프로세스를 처리하는 레이어로 (authentication 등)이 정의됩니다. 선택적 레이어
  3. pages: 애플리케이션의 페이지가 위치하는 레이어
  4. widgets: 페이지에 사용되는 독립적인 UI 컴포넌트이 위치하는 레이어
  5. features: 비즈니스 가치를 전달하는 사용자 시나리오와 기능이 위치하는 레이어 (좋아요, 리뷰 작성 등)
  6. entities: 비즈니스 Entities (e.g., User, Product, Order)
  7. shared: 특정 비즈니스 로직에 종속되지 않은 재사용 가능한 컴포넌트와 유틸리티(UI 키트, axios 설정, 애플리케이션 설정, 비즈니스 로직에 묶이지 않은 헬퍼 등)가 포함되어 있는 레이어

이러한 레이어를 분리한 이유는 FSD의 주요 특징 중 하나인 계층적 구조를 가지기 위함입니다. 이 구조에서는 features 레이어가 entities 레이어보다 더 상위에 위치하기 때문에 entities 레이어의 기능을 사용할 수 있습니다.

하지만 역은 성립하지 않습니다. entities 레이어에서는 자신의 레이어보다 상위에 위치한 features 레이어의 기능을 사용할 수 없습니다. 이는 한 방향으로만 향하는 선형적인 흐름을 유지하기 위함입니다.

계층 구조에서 레이어의 위치가 낮을수록, 기능 혹은 코드들이 더 많은 곳에서 사용될 가능성이 높기 때문에 레이어를 변경하는 것은 위험할 수 있습니다. 예를 들어 shared 레이어의 UI 키트는 모든 레이어에서 사용할 수 있기 때문에, 한 번 변경되면 재사용되고 있는 모든 곳을 확인해야하는 경우가 발생할 수 있습니다.

🤔 이러한 선형적인 흐름이 주는 장점이 무엇일까?

우선 선형적인 흐름을 가진 아키텍처의 경우 코드의 실행 흐름을 쉽게 이해할 수 있게 도와주기 때문에 코드의 유지 보수가 쉬워진다는 장점이 있습니다. 또한, 기존의 모듈식 아키텍처의 버그 추적과 비교하여 FSD는 코드의 각 단계에서 어떤 일이 일어나고 있는지를 명확하게 파악할 수 있으므로 버그를 추적하기 쉽습니다.

📦content-card
 ┣ 📂hooks
 ┃ ┗ 📜useLikeContentCard.tsx
 ┣ 📂tests
 ┃ ┗ 📜ContentCard.test.tsx
 ┗ 📂ui
 ┃ ┣ 📜ContentCard.scss
 ┃ ┣ 📜ContentCard.tsx
 ┃ ┣ 📜SkeletonContentCard.scss
 ┃ ┣ 📜SkeletonContentCard.tsx
 ┃ ┗ 📜SkeletonContentList.tsx

(추가) 이후 Slice와 Segment에서 이야기 할 내용이지만, 컴포넌트에서 사용되는 UI 키트, 보조 기능, API 호출, 테스트 코드 등이 다음과 같이 하나의 Slice로 묶이기 때문에 응집성이 강해집니다.

📕 Slice

슬라이는 FSD 계층 구조에서 두 번째 수준으로, 주요 목적은 제품, 비즈니스 또는 애플리케이션에 대한 의미에 따라 코드를 그룹화하는 것입니다.

슬라이스 이름은 애플리케이션의 비즈니스 도메인에 의해 직접 결정되므로 표준화되지 않습니다. 예를 들어 소셜 네트워크를 만든다고 하면 user, post, comment에 대한 슬라이스가 있을 수 있습니다.

밀접하게 관련된 슬라이스는 구조적으로 디렉터리에 그룹화될 수 있지만 다른 슬라이스와 동일한 격리 규칙을 적용해야 합니다. 해당 디렉터리에서는 코드가 공유 되어서는 안됩니다.

"코드가 공유되면 안된다"는 의미는 앞서 예시를 들었던 소셜 네트워크에서 user에서 post 슬라이스에 대한 기능을 공유해서는 안된다는 의미입니다. 이렇게 되면 도메인간의 관심사가 분리되지 않기 때문입니다.

그렇다면 이러한 슬라이스는 어떻게 다른 상위 레이어에서 참조할 수 있을까요? FSD에서는 상위 레이어에서 하위 레이어를 참조할 때, 하위 레이어의 공개 API를 사용합니다.

공개 API에 대한 내용은 Segment를 설명한 이후에 바로 알아보겠습니다.

📘 Segment

각 슬라이스는 세그먼트로 구성됩니다. 세그먼트는 목적에 따라 슬라이스 내의 코드를 나누는 데 도움이 됩니다. 팀의 합의에 따라 세그먼트의 구성과 이름이 변경될 수 있습니다. 일반적으로 사용되는 세그먼트들은 다음과 같습니다.

  • api: 필요한 서버 요청
  • ui: 슬라이스 ui 컴포넌트
  • model: 비즈니스 로직, 즉 상태와의 상호 작용. actions 및 selectors 등
  • lib: 슬라이스 내에서 사용되는 보조 기능
  • config: 슬라이스에 필요한 구성값이지만 구성 세그먼트는 거의 필요하지 않음
  • consts: 필요한 상수

앞서 Slice의 예시로 들었던 소셜 네트워크 중 post 슬라이스를 기준으로 세그먼트를 생성한다고 하면 다음과 같이 만들 수 있을 것입니다. (제 의견입니다!)

📦post
 ┗ 📂api
 ┃ ┗ 📜useGetPost.tsx 서버로 부터 특정 Post 정보를 받아오는 커스텀 훅
 ┗ 📂ui
 ┃ ┣ 📜Post.scss: 게시물 UI
 ┃ ┣ 📜Post.tsx: 게시물 UI
 ┃ ┣ 📜SkeletonPost.scss: 게시물 Skeleton UI
 ┃ ┗ 📜SkeletonPost.tsx: 게시물 Skeleton UI
 ┣ 📂model
 ┃ ┗ 📜usePostFilter.ts: 게시물 필터링
 ┣ 📂lib
 ┃ ┗ 📜convert.ts: 게시물에 표시되는 시간을 변환해주는 함수
 ┣ 📂tests (저는 해당 도메인에 테스트 코드를 추가하기 위해, tests 폴더를 추가하였습니다) 
 ┃ ┗ 📜Post.error.test.tsx: 에러 테스트
 ┃ ┗ 📜Post.test.tsx: 성공 테스트

위와 같은 구성은 과거 모듈식 아키텍처와 비교하였을 때, post 도메인 폴더 하위에 필요한 UI 키트와 기능들이 모여있기 때문에 응집도가 크게 향상되게 됩니다.

🔗 공개 API

FSD에서 공개 API의 역할은 바로 내부를 격리시키고, 필요한 기능만 외부로 추출하는 역할을 합니다. 실제 문서를 확인해보면 아래와 같이 설명하고 있습니다.

슬라이스 내에서 코드는 매우 자유롭게 구성될 수 있으며, 슬라이스가 격리되고 공개 API로만 접근이 가능하다면 큰 문제는 없습니다.

모든 슬라이스(및 슬라이스가 없는 레이어의 세그먼트)에는 공개 API 정의가 포함되어야 합니다.

이 슬라이스/세그먼트 외부의 모듈은 공개 API만 참조할 수 있으며 슬라이스/세그먼트의 내부 파일 구조는 참조할 수 없습니다.

공개 API를 이용하여 어떻게, 내부 파일 구조를 격리하고 필요한 기능만 숨기는 건지에 대해 FSD에서 제공해주는 공개 API 생성 모범 사례를 확인해 보겠습니다.

└── features/               # 
       ├── auth-form /      # Internal structure of the feature
       |     ├── ui/        #
       |     ├── model/     #
       |     ├── {...}/     #
       ├── index.ts         # Entrypoint features with its public API
// index.ts

export { Form as AuthForm } from "./ui"
export * as authFormModel from "./model"

위 features 하위에는 auth-form이 존재하고, 상위 레이어에서 auth-form에 접근하기 위해서는 반드시 index.ts를 통해서만 접근할 수 있습니다. 이러한 방식을 통해 FSD는 내부를 격리시키고, 필요한 기능만을 외부로 추출하는 목표를 수행합니다.

Public API를 어떻게 하면 더 FSD스럽게 만들 수 있을지, 그리고 Public API의 Goal은 무엇인지 등 더 깊은 내용은 Public API | Feature-Sliced Design를 통해 확인할 수 있습니다.

😵‍ FSD 너무 어려운데?

!! 맞는 말입니다.

짧게나마 FSD를 이용한 개인 프로젝트를 진행한 저 조차도 아직 낯설고 어렵습니다.. 해당 방법론의 러닝 커브가 높다는 사실을 부정할 수 없지만, FSD가 추구하는 목표와 명확한 비즈니스 지향성, 엔티티 정의, 애플리케이션의 기능 및 컴포넌트 구성과 같은 특징들로 인해 다른 아키텍처들 사이에서 돋보이는 것도 사실입니다.

단순히 어렵다는 이유 하나만으로, 도입하기를 주저하는 사람이 있다면 주저하지 않고 도입해보기를 정말 정말 권장합니다. 저의 경우에는 현재 진행하고 있는 프로젝트에서 FSD의 widgets, features, entities 레이어에 충족하는 구조를 가지고 진행하기에는 아직 어려운 부분이 많을 것 같다고 판단하여 아래와 같이 커스텀하여 진행하려고 계획하고 있습니다.

그러니 어렵더라도 함께 도전해봅시다! 이렇게 도전하는 과정에서 새로운, 더 나은 자기만의 아키텍처를 만들 수도 있는 거니까요!

참고

profile
Happy Day 😊❣️

0개의 댓글