아키텍처 패턴 — 클린 아키텍처와 폴더 구조
프로젝트가 커질수록 아키텍처의 중요성이 드러납니다. 처음부터 좋은 구조를 잡으면 유지보수가 쉬워집니다.
Feature-Based 구조 (권장)
src/
├── app/ # 앱 진입점, Provider, Navigation
│ ├── App.tsx
│ ├── navigation/
│ │ ├── RootNavigator.tsx
│ │ └── AuthNavigator.tsx
│ └── providers/
│ └── AppProviders.tsx
├── features/ # 기능별 모듈
│ ├── auth/
│ │ ├── screens/
│ │ │ ├── LoginScreen.tsx
│ │ │ └── RegisterScreen.tsx
│ │ ├── components/
│ │ │ └── LoginForm.tsx
│ │ ├── hooks/
│ │ │ └── useAuth.ts
│ │ ├── api/
│ │ │ └── authApi.ts
│ │ ├── store/
│ │ │ └── authStore.ts
│ │ └── types.ts
│ ├── home/
│ │ ├── screens/
│ │ ├── components/
│ │ └── hooks/
│ └── profile/
├── shared/ # 공유 모듈
│ ├── components/ # 공용 UI 컴포넌트
│ │ ├── Button.tsx
│ │ ├── Input.tsx
│ │ └── Card.tsx
│ ├── hooks/ # 공용 Hook
│ │ ├── useDebounce.ts
│ │ └── useAsync.ts
│ ├── utils/ # 유틸리티
│ │ ├── format.ts
│ │ └── validation.ts
│ ├── constants/ # 상수
│ │ └── config.ts
│ └── types/ # 공용 타입
│ └── common.ts
├── theme/ # 테마 시스템
│ ├── colors.ts
│ ├── typography.ts
│ └── spacing.ts
├── i18n/ # 다국어
├── api/ # API 클라이언트
│ └── client.ts
└── store/ # 전역 스토어
└── index.ts
레이어 분리
┌──────────────────────────────────┐
│ Presentation Layer │ ← Screen, Component
│ (UI, 사용자 인터랙션) │
├──────────────────────────────────┤
│ Application Layer │ ← Hook, Store
│ (비즈니스 로직, 상태 관리) │
├──────────────────────────────────┤
│ Data Layer │ ← API, Repository
│ (데이터 소스, API 통신) │
└──────────────────────────────────┘
의존성 방향: Presentation → Application → Data
Data Layer가 변해도 Presentation은 영향 없음
실전 예제
// Data Layer — API 호출
// features/auth/api/authApi.ts
export const authApi = {
login: (email: string, password: string) =>
apiClient.post<LoginResponse>('/auth/login', { email, password }).then(r => r.data),
getProfile: () =>
apiClient.get<User>('/auth/me').then(r => r.data),
};
// Application Layer — 비즈니스 로직
// features/auth/hooks/useLogin.ts
export function useLogin() {
const mutation = useMutation({
mutationFn: ({ email, password }: LoginForm) => authApi.login(email, password),
onSuccess: (data) => {
useAuthStore.getState().setToken(data.token);
},
});
return mutation;
}
// Presentation Layer — UI
// features/auth/screens/LoginScreen.tsx
export function LoginScreen() {
const { mutate: login, isPending } = useLogin();
// UI만 담당
return <LoginForm onSubmit={login} isLoading={isPending} />;
}
Import 규칙
// 절대 경로 설정 (tsconfig.json)
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@features/*": ["src/features/*"],
"@shared/*": ["src/shared/*"],
"@theme/*": ["src/theme/*"]
}
}
}
// 사용
import { Button } from '@shared/components/Button';
import { useAuth } from '@features/auth/hooks/useAuth';
import { colors } from '@theme/colors';
모듈 경계 규칙
- feature 간 직접 import 금지 — 공유 필요한 것은
shared/로 이동 - Screen은 Hook과 Component를 조합 하는 역할만
- API 호출은 반드시 api/ 폴더에서만 — 컴포넌트에서 직접 fetch 금지
- ** 비즈니스 로직은 Hook에서** — 컴포넌트는 UI에만 집중
정리
- Feature-Based 구조 로 기능별로 코드를 묶으면 관련 파일을 빠르게 찾을 수 있습니다
- Presentation, Application, Data 레이어를 분리하면 테스트와 교체가 쉽습니다
- ** 절대 경로 **를 설정하면 import가 깔끔해집니다
- feature 간 ** 직접 의존을 피하고** 공유 모듈을 통해 소통하세요
- 처음부터 완벽한 아키텍처를 만들려 하지 말고, ** 프로젝트 성장에 맞춰 진화 **시키세요
댓글 로딩 중...