Skip to content

아키텍처 가이드

Feature-Sliced Design (FSD)

이 프로젝트는 Feature-Sliced Design 아키텍처를 따릅니다.

핵심 원칙

  1. 레이어 기반 구조: 상위 레이어는 하위 레이어만 참조 가능
  2. 명확한 책임 분리: 각 레이어는 고유한 책임을 가짐
  3. 모듈 독립성: 같은 레벨의 모듈끼리는 독립적

프로젝트 구조

src/
├── app/              # 애플리케이션 초기화 및 전역 설정
├── pages/            # 페이지 컴포넌트
├── widgets/          # 독립적인 UI 위젯
├── features/         # 기능별 비즈니스 로직
├── entities/         # 비즈니스 엔티티 및 API
└── shared/           # 공통 유틸리티 및 UI 컴포넌트

레이어별 역할

1. app/

역할: 애플리케이션 진입점 및 전역 설정

app/
├── index.tsx          # 진입점
├── App.tsx            # 메인 앱 컴포넌트
├── layouts/           # 레이아웃
│   ├── AuthLayout.tsx
│   └── BaseLayout.tsx
├── providers/         # 전역 프로바이더
│   ├── AppProvider.tsx
│   ├── ToastProvider.tsx
│   ├── ConfirmProvider.tsx
│   └── ThemeProvider.tsx
└── routers/           # 라우팅 설정
    ├── rootRouter.tsx
    ├── authRoutes.tsx
    ├── mainRoutes.tsx
    └── guards/
        └── AuthenticationGuard.tsx

포함 내용:

  • React Router 설정
  • 전역 Context Provider
  • 레이아웃 컴포넌트
  • 인증 가드

규칙:

  • 비즈니스 로직 작성 금지
  • UI 컴포넌트는 레이아웃만

2. pages/

역할: URL 라우트와 1:1 매핑되는 페이지 컴포넌트

pages/
├── Dashboard/
│   ├── Dashboard.tsx
│   └── Dashboard.css.ts
├── UserManagement/
├── Processing/
├── Storage/
└── ...

포함 내용:

  • 페이지 레벨 컴포넌트
  • widgets, features 조합
  • 페이지 레벨 상태 관리

규칙:

  • 비즈니스 로직은 features로 위임
  • API 호출은 entities 사용
  • 재사용 가능한 로직은 features로 분리

3. widgets/

역할: 페이지를 구성하는 독립적인 대형 UI 블록

포함 내용:

  • 재사용 가능한 독립 UI 블록
  • features와 entities 조합
  • 자체 상태 관리

규칙:

  • 다른 widgets 의존 금지
  • features와 entities 사용 가능
  • shared UI만 직접 사용

4. features/

역할: 사용자 시나리오와 비즈니스 로직

features/
├── user/
│   ├── ui/            # UI 컴포넌트
│   │   ├── SignInForm/
│   │   ├── SignUpForm/
│   │   └── UserTable/
│   ├── model/         # 비즈니스 로직 (커스텀 훅)
│   │   ├── useSignIn.ts
│   │   ├── useSignUp.ts
│   │   └── useCurrentUser.ts
│   ├── utils/         # 유틸리티 함수
│   │   └── validation.ts
│   └── constants/     # 상수
│       └── options.ts
├── processing/
│   ├── ui/
│   ├── model/
│   ├── utils/
│   └── constants/
└── ...

포함 내용:

  • 기능별 UI 컴포넌트
  • 커스텀 훅 (비즈니스 로직)
  • 기능별 유틸리티
  • 기능별 상수

규칙:

  • entities의 API 사용
  • shared 사용 가능
  • 다른 features 의존 금지

5. entities/

역할: 비즈니스 도메인 엔티티 및 API

entities/
├── auth/
│   ├── api/
│   │   ├── auth.api.ts
│   │   └── auth.dto.ts
│   └── model/
│       └── auth.model.ts
├── user/
│   ├── api/
│   │   ├── user.api.ts
│   │   └── user.dto.ts
│   └── model/
│       └── user.model.ts
└── ...

포함 내용:

  • API 함수
  • DTO (Data Transfer Object)
  • 도메인 모델
  • 타입 정의

규칙:

  • UI 컴포넌트 작성 금지
  • 순수 데이터 레이어
  • shared만 사용 가능

6. shared/

역할: 프로젝트 전반에서 재사용되는 코드

shared/
├── ui/               # 디자인 시스템
│   ├── atoms/        # Button, Input, Card 등
│   ├── molecules/    # Modal, Table, Select 등
│   └── organisms/    # VideoPlayer, Toast 등
├── hooks/            # 공통 커스텀 훅
│   ├── useForm.ts
│   ├── useToast.ts
│   └── useModal.ts
├── utils/            # 유틸리티 함수
│   ├── datetime.ts
│   ├── string.ts
│   └── sort.ts
├── api/              # API 클라이언트
│   ├── base.ts
│   └── error-handler.ts
├── types/            # 공통 타입
│   └── page.ts
├── config/           # 설정
│   ├── routes.ts
│   ├── queryKeys.ts
│   └── regex.ts
├── styles/           # 디자인 토큰
│   ├── theme.css.ts
│   ├── light.ts
│   └── dark.ts
└── lib/              # 라이브러리
    ├── websocket/
    └── i18n/

포함 내용:

  • 디자인 시스템 (Atomic Design)
  • 공통 훅
  • 유틸리티 함수
  • API 클라이언트
  • 전역 설정

규칙:

  • 비즈니스 로직 작성 금지
  • 순수 함수 및 범용 컴포넌트만
  • 다른 레이어 참조 금지

의존성 규칙

레이어 간 의존성

app

pages

widgets

features

entities

shared

규칙:

  • 상위 레이어만 하위 레이어 참조 가능
  • 하위 레이어는 상위 레이어 참조 금지

디렉토리 구조 예시

Feature 구조

features/user/
├── ui/
│   ├── SignInForm/
│   │   ├── SignInForm.tsx
│   │   ├── SignInForm.css.ts
│   │   └── SignInForm.test.tsx
│   ├── UserTable/
│   │   ├── UserTable.tsx
│   │   ├── UserTable.css.ts
│   │   └── UserTableColumns.tsx
│   └── index.ts
├── model/
│   ├── useSignIn.ts
│   ├── useSignUp.ts
│   ├── useCurrentUser.ts
│   └── index.ts
├── utils/
│   ├── validation.ts
│   └── index.ts
├── constants/
│   ├── options.ts
│   └── index.ts
└── index.ts

Entity 구조

entities/user/
├── api/
│   ├── user.api.ts
│   ├── user.dto.ts
│   └── index.ts
├── model/
│   ├── user.model.ts
│   └── index.ts
└── index.ts

Public API (index.ts)

각 모듈은 index.ts로 public API를 노출합니다.

typescript
// features/user/index.ts
export { default as SignInForm } from './ui/SignInForm/SignInForm';
export { default as UserTable } from './ui/UserTable/UserTable';
export { useSignIn, useSignUp, useCurrentUser } from './model';
typescript
// entities/user/index.ts
export * from './api/user.api';
export * from './model/user.model';
export type * from './api/user.dto';

사용:

typescript
// ✅ Public API 사용
import { SignInForm, useSignIn } from '@features/user';
import { getCurrentUser, type UserDto } from '@entities/user';

// ❌ 직접 경로 사용 금지
import SignInForm from '@features/user/ui/SignInForm/SignInForm';

실전 예시

페이지 구성

typescript
// pages/UserManagement/UserManagement.tsx
import { UserTable } from '@features/user';
import { useUserPage } from '@features/user';
import { SearchBar } from '@shared/ui';

export default function UserManagement() {
  const { users, keyword, setKeyword, sort, setSort } = useUserPage();

  return (
    <div>
      <SearchBar value={keyword} onChange={setKeyword} />
      <UserTable users={users} sort={sort} onSort={setSort} />
    </div>
  );
}

Feature 구현

typescript
// features/user/model/useUserPage.ts
import { useInfiniteQuery } from '@tanstack/react-query';
import { getUserPage } from '@entities/user';
import { queryKeys } from '@shared/config/queryKeys';

export function useUserPage() {
  const [keyword, setKeyword] = useState('');

  const query = useInfiniteQuery({
    queryKey: queryKeys.user.list({ keyword }),
    queryFn: ({ pageParam = 0 }) => getUserPage({ page: pageParam, keyword }),
    getNextPageParam: (lastPage) => (lastPage.last ? undefined : lastPage.number + 1),
  });

  const users = query.data?.pages.flatMap((page) => page.content) || [];

  return {
    users,
    keyword,
    setKeyword,
    ...query,
  };
}

Entity 구현

typescript
// entities/user/api/user.api.ts
import { api } from '@shared/api/base';
import type { UserDto, PageRequest, PageResponse } from './user.dto';

export async function getUserPage(request: PageRequest): Promise<PageResponse<UserDto>> {
  const { data } = await api.get<PageResponse<UserDto>>('/api/users', {
    params: request,
  });
  return data;
}

마이그레이션 가이드

기존 코드를 FSD로 마이그레이션하는 순서:

  1. shared 분리: 공통 UI, 유틸, API 클라이언트
  2. entities 생성: API 함수, DTO, 모델
  3. features 생성: 비즈니스 로직, UI 컴포넌트
  4. pages 정리: features 조합으로 단순화
  5. app 정리: 프로바이더, 라우팅만 유지

참고 자료