-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Description
어떤 기능인가요?
추가하려는 기능에 대해 간결하게 설명해주세요
🏗️ Clean FSD & CQRS 아키텍처 가이드 (v1.0)
1. 핵심 철학 (Core Philosophy)
우리는 **Clean FSD (Feature-Sliced Design)**를 기반으로, CQRS (Read/Write 분리) 패턴과 Next.js App Router의 장점을 결합한 아키텍처를 따릅니다.
- Lego Architecture: 하위 레이어(Entities, Features)는 독립적인 레고 블록이며, 상위 레이어(Widgets, Pages)는 이를 조립하는 역할을 합니다.
- Server First: 읽기(Read) 동작은 **RSC(Server Component)**를 통해 최적화하고, 쓰기(Write) 동작은 Client Component로 상호작용을 처리합니다.
- Separation of Concerns: 프레임워크 설정(
app)과 비즈니스 화면 구현(pages)을 철저히 분리합니다.
2. 디렉토리 구조 (Directory Structure)
2.1. 전체 트리 (Overview)
src/
├── app/ # [Framework Layer] 라우팅, 메타데이터, 레이아웃 (Shell)
├── pages/ # [Page Layer] 실제 페이지 화면 조립 (Implementation)
├── widgets/ # [Composition Layer] Feature와 Entity를 결합한 독립 블록
├── features/ # [Write Layer] 사용자 행동, 뮤테이션 (Verbs)
├── entities/ # [Read Layer] 도메인 데이터, 조회 (Nouns)
└── shared/ # [Generic Layer] 도메인을 모르는 순수 공통 로직
2.2. 레이어별 상세 역할
| 레이어 | 역할 | 성격 | 주요 포함 내용 | 참조 가능 범위 |
|---|---|---|---|---|
| App | 라우팅 (Shell) | Framework | page.tsx, layout.tsx, Metadata | pages 이하 전체 |
| Pages | 화면 조립 | Business | 페이지 단위 UI 조합 | widgets 이하 전체 |
| Widgets | 블록 조립 | Composition | Header, PostDetail, Sidebar | features, entities, shared |
| Features | 쓰기 (Write) | Action (CUD) | 로그인 폼, 좋아요 버튼, useMutation | entities, shared |
| Entities | 읽기 (Read) | Data (R) | 유저 프로필 뷰, fetchUser, Type | shared |
| Shared | 공통 | Generic | Button, Axios, Utils | 불가 (최하위) |
5. 구현 가이드 (Implementation Guide)
5.1. App vs Pages 분리 패턴
Next.js의 라우팅과 비즈니스 로직을 분리하여 유지보수성을 극대화합니다.
1. src/app/post/[id]/page.tsx (껍데기)
import { Metadata } from 'next';
import { PostPage } from '@/pages/post-detail'; // 실제 구현체 import
import { getPost } from '@/entities/post/api'; // API import
// 메타데이터용 Fetch (Request Memoization으로 중복 호출 걱정 X)
export async function generateMetadata({ params }): Promise<Metadata> {
const data = await getPost(params.id);
return { title: data.title };
}
// Params만 전달하고 빠짐
export default function Page({ params }) {
return <PostPage id={params.id} />;
}2. src/pages/post-detail/ui/PostPage.tsx (알맹이)
import { PostViewerWidget } from '@/widgets/PostViewer';
// 순수 비즈니스 로직 및 조립
export const PostPage = ({ id }) => {
return (
<main>
<PostViewerWidget postId={id} />
</main>
);
};5.2. CQRS 패턴 적용 예시
상황: 게시글을 보고(Read), 좋아요를 누른다(Write).
-
Read (Entity):
entities/postapi/getPost.ts: 데이터를 가져옴.ui/PostContent/: 데이터를 보여주기만 함 (Server Component 권장).
-
Write (Feature):
features/post-interactionapi/likePost.ts: 서버에 좋아요 요청을 보냄.ui/LikeButton/: 클릭 이벤트를 받음 (Client Component 필수).
-
Composition (Widget):
widgets/PostViewerEntity와Feature를 import 하여 한 덩어리로 만듦.props를 통해 Entity의 ID를 Feature에게 전달.
6. 리팩토링 체크리스트 (Rule of Two)
코드를 작성하다가 아래 상황이 발생하면 리팩토링을 진행합니다.
-
중복 발견: 특정 UI나 로직이 2곳 이상의 슬라이스에서 똑같이 필요하다.
- → 도메인 로직이 없으면 **
shared**로 이동. - → 도메인 로직이 있으면 **
widgets**으로 이동.
- → 도메인 로직이 없으면 **
-
참조 위반:
import경로가 상위로 가거나, 옆집(다른 슬라이스)을 가리킨다.- → 해당 로직을 **상위 레이어(
Widgets,Pages)**로 끌어올려서 조합하는 형태로 변경.
- → 해당 로직을 **상위 레이어(
-
파일 비대화: 하나의 컴포넌트 파일이 너무 길어진다.
- → 내부 로직은
model/useLogic.ts로 분리. - → 하위 UI는 같은 폴더 내 컴포넌트로 분리.
- → 내부 로직은
Metadata
Metadata
Assignees
Labels
No labels