Skip to content

[5팀 김유현] Chapter 2-3. 관심사 분리와 폴더구조#33

Open
yuhyeon99 wants to merge 2 commits intohanghae-plus:mainfrom
yuhyeon99:main
Open

[5팀 김유현] Chapter 2-3. 관심사 분리와 폴더구조#33
yuhyeon99 wants to merge 2 commits intohanghae-plus:mainfrom
yuhyeon99:main

Conversation

@yuhyeon99
Copy link

@yuhyeon99 yuhyeon99 commented Aug 13, 2025

과제 체크포인트

기본과제

목표 : 전역상태관리를 이용한 적절한 분리와 계층에 대한 이해를 통한 FSD 폴더 구조 적용하기

  • 전역상태관리를 사용해서 상태를 분리하고 관리하는 방법에 대한 이해
  • Context API, Jotai, Zustand 등 상태관리 라이브러리 사용하기
  • FSD(Feature-Sliced Design)에 대한 이해
  • FSD를 통한 관심사의 분리에 대한 이해
  • 단일책임과 역할이란 무엇인가?
  • 관심사를 하나만 가지고 있는가?
  • 어디에 무엇을 넣어야 하는가?

체크포인트

  • 전역상태관리를 사용해서 상태를 분리하고 관리했나요?
  • Props Drilling을 최소화했나요?
  • shared 공통 컴포넌트를 분리했나요?
  • shared 공통 로직을 분리했나요?
  • entities를 중심으로 type을 정의하고 model을 분리했나요?
  • entities를 중심으로 ui를 분리했나요?
  • entities를 중심으로 api를 분리했나요?
  • feature를 중심으로 사용자행동(이벤트 처리)를 분리했나요?
  • feature를 중심으로 ui를 분리했나요?
  • feature를 중심으로 api를 분리했나요?
  • widget을 중심으로 데이터를 재사용가능한 형태로 분리했나요?

심화과제

목표: 서버상태관리 도구인 TanstackQuery를 이용하여 비동기코드를 선언적인 함수형 프로그래밍으로 작성하기

  • TanstackQuery의 사용법에 대한 이해
  • TanstackQuery를 이용한 비동기 코드 작성에 대한 이해
  • 비동기 코드를 선언적인 함수형 프로그래밍으로 작성하는 방법에 대한 이해

체크포인트

  • 모든 API 호출이 TanStack Query의 useQuery와 useMutation으로 대체되었는가?
  • 쿼리 키가 적절히 설정되었는가?
  • fetch와 useState가 아닌 선언적인 함수형 프로그래밍이 적절히 적용되었는가?
  • 캐싱과 리프레시 전략이 올바르게 구현되었는가?
  • 낙관적인 업데이트가 적용되었는가?
  • 에러 핸들링이 적절히 구현되었는가?
  • 서버 상태와 클라이언트 상태가 명확히 분리되었는가?
  • 코드가 간결하고 유지보수가 용이한 구조로 작성되었는가?
  • TanStack Query의 Devtools가 정상적으로 작동하는가?

최종과제

  • 폴더구조와 나의 멘탈모데일이 일치하나요?
  • 다른 사람이 봐도 이해하기 쉬운 구조인가요?

과제 셀프회고

해외 출장 이슈로 과제를 진행하지 못 했습니다..

  • 업무 중에 응대를 계속 해야해서 잠을 잘 자려고 하다보니 과제를 진행할 시간이 없었어요 ㅠ

이번 과제를 통해 이전에 비해 새롭게 알게 된 점이 있다면 적어주세요.

  • FSD의 핵심 개념들(Layer, Slice, Segment)에 대해서 새롭게 알게 되었습니다.

본인이 과제를 하면서 가장 애쓰려고 노력했던 부분은 무엇인가요?

아직은 막연하다거나 더 고민이 필요한 부분을 적어주세요.

이번에 배운 내용 중을 통해 앞으로 개발에 어떻게 적용해보고 싶은지 적어주세요.

챕터 셀프회고

클린코드와 아키테쳑 챕터 함께 하느라 고생 많으셨습니다!
지난 3주간의 여정을 돌이켜 볼 수 있도록 준비해보았습니다.
아래에 적힌 질문들은 추억(?)을 회상할 수 있도록 도와주려고 만든 질문이며, 꼭 질문에 대한 대답이 아니어도 좋으니 내가 느꼈던 인사이트들을 자유롭게 적어주세요.

클린코드: 읽기 좋고 유지보수하기 좋은 코드 만들기

  • 더티코드를 접했을 때 어떤 기분이었나요? ^^; 클린코드의 중요성, 읽기 좋은 코드란 무엇인지, 유지보수하기 쉬운 코드란 무엇인지에 대한 생각을 공유해주세요
    • 어느 것부터 손 봐야할 지 너무 막막했습니다.
    • AI Driven Refactoring을 했지만.. 점차 감이 잡혀가서 2-3 과제에서는 가급적이면 AI의 힘을 빌리지 않고 작업해보고 싶습니다.

결합도 낮추기: 디자인 패턴, 순수함수, 컴포넌트 분리, 전역상태 관리

  • 거대한 단일 컴포넌트를 봤을때의 느낌! 처음엔 막막했던 상태관리, 디자인 패턴이라는 말이 어렵게만 느껴졌던 시절, 순수함수로 분리하면서 "아하!"했던 순간, 컴포넌트가 독립적이 되어가는 과정에서의 깨달음을 들려주세요
    • 거대 컴포넌트 내부에서 어떤 함수가 훅으로 빠져야 하고, 어떤 함수가 유틸 함수로 빠져야 하는지 등 역할 분담에 대해서 깨달은 부분이 많았습니다.

응집도 높이기: 서버상태관리, 폴더 구조

  • "이 코드는 대체 어디에 둬야 하지?"라고 고민했던 시간, FSD를 적용해보면서의 느낌, 나만의 구조를 만들어가는 과정, TanStack Query로 서버 상태를 분리하면서 느낀 해방감(?)등을 공유해주세요

리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문

Copy link
Contributor

@JunilHwang JunilHwang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요, 김유현 님! 5주차 과제 잘 진행해주셨네요. 고생하셨습니다 👍🏻👍🏻
현재 남기는 코멘트는 GPT-5-mini model을 사용하여 AI가 남기는 피드백입니다 🤖
과제 피드백과는 별개이므로 참고해주세요!

1. 🏗️ FSD 아키텍처

💡 개념 정의

Feature-Sliced Design(FSD)은 기능(Feature)별로 관심사를 분리하고, 계층을 명확히 하여 변경에 의한 파급을 최소화하는 아키텍처 원칙입니다. 일반적으로 app → pages → widgets → features → entities → shared 순서로 의존성이 흐르도록 설계합니다.

⚡ 중요성

FSD를 통해 새로운 기능 추가, 아키텍처 변경(모노레포, 마이크로프론트엔드), UI 라이브러리 교체 시 최소한의 파일만 수정하면 되어 유지보수성과 확장성이 크게 향상됩니다.

📊 현재 상황 분석

PostsManagerPage.tsx에 서버 호출(fetch), 로컬 상태(useState로 서버/클라이언트 상태 혼합), 라우터 파라미터 동기화, 여러 다이얼로그 렌더링이 모두 혼재. shared/ui는 components/index.tsx로 추상화되어 있어 디자인 변경 대응은 상대적으로 용이하지만, 핵심 도메인 로직이 분산되지 않아 FSD의 이점을 못 받고 있습니다.

📝 상세 피드백

개요 및 평가 요약:
현재 코드베이스는 FSD(Feature-Sliced Design)의 일부 원칙(components, pages 분리)을 따르고 있으나 실제 계층 책임 분리가 미흡합니다. PostsManagerPage.tsx 파일에 UI, 비즈니스 로직, 서버 통신, 라우팅 URL 동기화, 모달 상태 등 거의 모든 관심사가 한 파일에 집중되어 있어 FSD의 계층(entities/features/widgets/shared/pages/app) 방향성(상위→하위)과 역할 분리가 지켜지지 않습니다.
핵심 문제: 단일 페이지가 entities(도메인), feature(행동), shared(UI) 책임을 혼합하고 있어 변경의 파급 범위가 큽니다. 예: API 엔드포인트 변경 또는 서버 상태 관리 전략 변경 시 PostsManagerPage.tsx 내 수십 지점을 수정해야 할 가능성이 큽니다.
권장 TO-BE 구조 요약:

  • entities/post: types, api (postApi.getPosts, postApi.getByTag 등), model (데이터 가공)
  • features/posts-list: usePosts hook (서버 상태/쿼리 키), UI 조합(테이블 + 페이징 로직)
  • widgets/post-detail / post-editor: 재사용 가능한 UI/상태(로컬 UI 상태)
  • shared/ui: Button, Select 같은 디자인 컴포넌트(현재 존재)
    정량적 근거/시나리오:
  • AS-IS: PostsManagerPage.tsx에 약 20개의 fetch 호출 지점(추정). API 클라이언트 교체 시 약 20개 파일 내(실제로 1파일 내 20지점) 수정 필요.
  • TO-BE: postApi 한 곳으로 추상화하면 API 교체 시 1파일 수정(entities/post/api/postApi.ts)로 해결.
    구체적 개선 단계(예상 소요):
  1. 단기(1일): PostsManagerPage.tsx에서 fetch 호출을 추출해 entities/post/api/postApi.ts 생성. (변경범위: PostsManagerPage에서 호출부 교체)
  2. 중기(1-2일): usePosts/usePostByTag/useComments 등 커스텀 훅으로 추출(Features 레이어). Post 목록 렌더링은 features/posts-list에서 담당.
  3. 장기(2-4일): 모듈화 및 테스트 추가, 폴더 구조 정리(FSD 표준에 맞춘 slice 생성).

❌ 현재 구조 (AS-IS)

AS-IS 예시 (PostsManagerPage.tsx 발췌):
fetch(`/api/posts?limit=${limit}&skip=${skip}`)
  .then((response) => response.json())
  .then((data) => { /* ... */ })
// 컴포넌트 내부에 fetch/then 체인이 여러 곳 존재, UI/상태/로직 혼재

✅ 권장 구조 (TO-BE)

TO-BE 예시:
// entities/post/api/postApi.ts
export const postApi = {
  getPosts: (params) => httpClient.get('/posts', { params }),
  getByTag: (tag) => httpClient.get(`/posts/tag/${tag}`),
  addPost: (payload) => httpClient.post('/posts/add', payload),
}

// features/posts/hooks/usePosts.ts (TanStack Query 사용)
export const usePosts = (params) => useQuery(queryKeys.posts(params), () => postApi.getPosts(params))

// pages/PostsManagerPage.tsx에서는 usePosts를 사용해 UI에 데이터를 주입만 함

🔄 변경 시나리오별 영향도

  1. 디자인 시스템(Material UI 등) 변경: shared/ui만 교체하면 UI 변경량은 적음(components/index.tsx가 추상화 레이어 역할을 함).
  2. API 엔드포인트 변경: AS-IS는 PostsManagerPage.tsx 내부의 모든 fetch 호출 수정(약 15~25곳). TO-BE는 entities/post/api 단 한 곳 수정.
  3. Features 추가(예: 오프라인 모드): 현재 구조는 PostsManagerPage에 많은 책임이 있으므로 오프라인 캐시 도입 시 수많은 로직을 변경해야 함; 분리하면 feature별 훅/스토어만 확장하면 됨.

🚀 개선 단계

  • 1단계: 단계 1 (1일): 모든 fetch 호출을 entities/post/api/postApi.ts 로 이전. PostsManagerPage에서는 postApi를 호출하도록 변경.
  • 2단계: 단계 2 (1-2일): 서버 상태는 TanStack Query 훅(usePosts, usePost, useComments)으로 전환하고 컴포넌트는 훅을 통해 데이터만 소비하도록 변경.
  • 3단계: 단계 3 (1-2일): UI(모달/폼)은 widgets 또는 features 폴더로 분리. 각 모듈은 단일 책임을 갖게 함.
  • 4단계: 단계 4 (2-4일): 폴더구조를 FSD 규칙에 맞춰 정리 및 문서화, 라우트/페이지는 최소한의 조합 레이어로 유지.

2. 🔄 TanStack Query

💡 개념 정의

TanStack Query는 서버 상태 관리를 선언적, 캐시 기반으로 처리하는 라이브러리입니다. useQuery/useMutation 훅을 통해 데이터 페칭, 캐싱, 리패칭, 에러 및 로딩 상태 관리를 중앙화합니다.

⚡ 중요성

복잡한 비동기 흐름을 간결하게 만들고, 캐싱/리패칭 전략을 통해 성능 개선과 네트워크 비용 절감을 제공합니다. 또한 API 변경 시 쿼리 키와 API 래퍼만 수정하면 되므로 확장성과 유지보수성이 향상됩니다.

📊 현재 상황 분석

PostsManagerPage.tsx 내부에 여러 fetch 호출 및 상태관리(useState)가 존재합니다. 예: fetch(/api/posts?limit=${limit}&skip=${skip}) 등. 쿼리 키가 없고, 캐싱/낙관 업데이트 전략이 전혀 없습니다.

📝 상세 피드백

개요 및 평가 요약:
심화과제로 요구된 TanStack Query 적용이 PR 내에 거의 적용되어 있지 않습니다. 현재 PostsManagerPage.tsx에서는 fetch + useState/useEffect 패턴을 사용해 서버 데이터를 처리하고 있어 선언적 서버 상태 관리의 장점을 활용하지 못하고 있습니다.
핵심 문제: 쿼리 키의 일관성 부재, API 호출의 분산, 캐싱/리패칭 전략 부재, 낙관적 업데이트 없음. 변화 시 수정範囲이 큼(예: 에러 처리 전략 변경 또는 새로운 데이터 소스 추가).
권장 사항 요약:

  • queryKeys 팩토리화 (shared/api/queryKeys.js)
  • entities/*/api에 순수 CRUD 함수 집약
  • features/*/api에서 useQuery/useMutation 래핑(비즈니스 로직 분리)
  • 캐싱 전략(staleTime, cacheTime)과 에러/로딩 처리를 일관되게 설정
  • 낙관적 업데이트는 useMutation의 onMutate/onError/onSettled를 사용하여 구현
    구체적 TO-BE 코드 예시 제공 및 단계별 개선 계획 포함.

❌ 현재 구조 (AS-IS)

AS-IS (PostsManagerPage.tsx 발췌):
const fetchPosts = () => {
  setLoading(true);
  fetch(`/api/posts?limit=${limit}&skip=${skip}`)
    .then((r) => r.json())
    .then((data) => { /* setPosts, setTotal */ })
    .finally(() => setLoading(false));
}
// 여러 곳에 동일 패턴 반복

✅ 권장 구조 (TO-BE)

TO-BE (TanStack Query 적용 예시):
// shared/api/queryKeys.ts
export const queryKeys = { posts: (params) => ['posts', params] };

// entities/post/api/postApi.ts
export const postApi = { getPosts: (params) => httpClient.get('/posts', { params }) };

// features/posts/hooks/usePosts.ts
export const usePosts = (params) => useQuery({
  queryKey: queryKeys.posts(params),
  queryFn: () => postApi.getPosts(params),
  staleTime: 1000 * 60, // 1분
});

// 페이지
const { data, isLoading } = usePosts({ skip, limit });

🔄 변경 시나리오별 영향도

  1. API 엔드포인트 변경: AS-IS는 페이지 내 모든 fetch 호출을 찾아 변경해야 함(약 15~25곳). TO-BE는 postApi만 수정하면 됨.
  2. 새 데이터 소스(예: 외부 사용자 서비스) 추가: useQuery 훅을 추가하고 queryKeys와 postApi를 연결하면 페이지 수정 최소화.
  3. 에러 처리 방식 변경(글로벌 to 지역): TanStack Query 설정과 onError 핸들러만 조정하면 전역 정책 적용 가능.

🚀 개선 단계

  • 1단계: 단계 1 (반나절): queryKeys 유틸 생성 및 postApi로 fetch 호출 이동.
  • 2단계: 단계 2 (1일): usePosts/usePostByTag/useComments 등 TanStack Query 훅 구현 및 PostsManagerPage에서 대체.
  • 3단계: 단계 3 (반나절): useMutation으로 add/update/delete 구현, 낙관적 업데이트(onMutate/onError rollback) 적용.
  • 4단계: 단계 4 (반나절): Devtools 추가, 전역 에러 핸들러 설정 및 문서화.

3. 🎯 응집도 (Cohesion)

💡 개념 정의

응집도(Cohesion)는 모듈 내부 요소들이 얼마나 관련성 높게 묶여 있는지를 뜻합니다. 높은 응집도는 '같은 이유로 변경되는 코드'가 한 곳에 모여 있어 유지보수가 쉽습니다.

⚡ 중요성

응집도가 높으면 한 기능을 변경할 때 수정 범위가 작아지고 테스트가 쉬워지며, 모듈 단위로 패키지화/분리(예: 도메인 패키지화)가 용이합니다.

📊 현재 상황 분석

파일이 비대해 유지보수성과 온보딩 난이도가 높습니다. 기능 단위로 분리하면 새 기능(예: 댓글 알림, 실시간 업데이트) 통합이 훨씬 쉬워집니다.

📝 상세 피드백

개요 및 평가 요약:
현재 응집도는 낮습니다. '게시물' 관련 기능(목록, 페이징, 검색, 태그 필터링, 댓글, 사용자 모달, CRUD)이 PostsManagerPage.tsx 한 파일에 섞여 있어, 하나의 기능 변경(예: 댓글 엔드포인트 변경)이 여러 코드 블록을 건드리게 합니다.
왜 문제인가?
응집도가 낮으면 변경 시 수정 파일 수 증가, 버그 유입, 테스트 난이도 상승 등이 발생합니다. 예를 들어 댓글 구조가 변경되면 댓글 관련 로직과 댓글을 표시하는 UI, 댓글 추가/삭제/좋아요 로직이 분리되어 있지 않으므로 여러 곳을 수정해야 합니다.
개선 방향(TO-BE):

  • 댓글 관련 기능은 features/comments로 묶어 useComments 훅과 CommentsWidget으로 분리
  • 게시물 목록 + 페이징은 features/posts-list에서 담당
  • API는 entities/post/api에 집중

❌ 현재 구조 (AS-IS)

AS-IS: Comments 로직과 UI가 같은 파일에 섞여 있음:
const fetchComments = async (postId) => { /* fetch & setComments */ }
const renderComments = (postId) => (/* UI + 버튼 핸들러(좋아요, 수정, 삭제) */)

✅ 권장 구조 (TO-BE)

TO-BE:
// features/comments/useComments.ts
export const useComments = (postId) => useQuery(queryKeys.comments(postId), () => commentApi.getByPost(postId));

// widgets/CommentsWidget.tsx
const CommentsWidget = ({ postId }) => {
  const { data: comments } = useComments(postId);
  return <CommentsList comments={comments} />;
};

🔄 변경 시나리오별 영향도

  1. 댓글 구조 변경: AS-IS는 PostsManagerPage 내 댓글 관련 여러 지점을 수정해야 함. TO-BE는 features/comments만 수정.
  2. 검색 로직 변경(서버 vs 클라이언트): 현재 검색 로직이 페이지에 섞여 있어 변경 시 리스크가 큼; 검색 훅으로 이전하면 영향 범위 축소.

🚀 개선 단계

  • 1단계: 단계 1 (반나절): 댓글 관련 함수(fetchComments, addComment, updateComment, deleteComment, likeComment)를 entities/comment/api로 이동.
  • 2단계: 단계 2 (1일): comments 훅(useComments)과 CommentsWidget 분리, PostsManagerPage에서는 위젯 조합만 담당.
  • 3단계: 단계 3 (1일): 게시물 편집/추가/삭제도 각 feature로 분리하여 응집도 향상.

4. 🔗 결합도 (Coupling)

💡 개념 정의

결합도(Coupling)는 모듈 간 의존성의 강도를 말합니다. 낮은 결합도는 한 모듈의 변경이 다른 모듈에 미치는 영향을 줄여 유연성을 높입니다.

⚡ 중요성

낮은 결합도는 기술 스택 교체(예: axios→fetch), 아키텍처 변화(모노레포 분리), 외부 서비스 변경에 더 유연하게 대응할 수 있게 합니다.

📊 현재 상황 분석

HTTP 클라이언트 변경 시 약 15~25 지점의 수정이 필요할 것으로 보이며, 이는 큰 리팩토링 부담을 의미합니다. 의존성 주입이나 추상화가 없으므로 테스트 시 전역 fetch mocking에 의존해야 합니다.

📝 상세 피드백

개요 및 평가 요약:
결합도는 비교적 높은 편입니다. PostsManagerPage.tsx가 구체적인 fetch 구현과 window URL 동기화, 직접 DOM 이벤트 처리를 포함하고 있어 다른 모듈(예: auth, http 클라이언트)으로의 교체가 어렵습니다.
핵심 문제: HTTP 클라이언트(현재 fetch)와 컴포넌트가 직접 결합되어 있어 axios나 다른 클라이언트로 교체할 때 많은 지점을 수정해야 합니다. 또한 알림/로깅(analytics)과 같은 부가 기능을 주입할 수 있는 추상화가 없습니다.
개선 방향:

  • http client abstraction(httpClient) 도입(axios/fetch wrapper) 및 DI 패턴 적용
  • postApi, userApi 등의 인터페이스에 의존하도록 변경
  • 훅이나 props로 콜백(onSuccess/onError)을 주입해 UI-로직 결합 완화

❌ 현재 구조 (AS-IS)

AS-IS 예시:
await fetch('/api/posts/add', { method: 'POST', headers: {...}, body: JSON.stringify(newPost) })
// 컴포넌트 내부에 직접 fetch 사용

✅ 권장 구조 (TO-BE)

TO-BE 예시:
// shared/http/httpClient.ts
export const httpClient = { get: (url, opts) => fetch(url, opts).then(r => r.json()), post: (...) }

// entities/post/api/postApi.ts
export const postApi = { addPost: (payload) => httpClient.post('/posts/add', payload) }

🔄 변경 시나리오별 영향도

  1. HTTP 클라이언트 교체(axios→fetch): AS-IS는 각 fetch 호출 수정 필요(약 15~25곳). TO-BE는 httpClient wrapper 1곳 수정.
  2. 상태관리 라이브러리 변경(redux→zustand): 현재는 로컬 useState 기반이라 migration 범위는 페이지 단위이지만 전역 상태가 확장되면 추상화 필요.

🚀 개선 단계

  • 1단계: 단계 1 (반나절): httpClient wrapper 생성 (get/post/put/delete) 및 기존 fetch 호출을 httpClient로 전환.
  • 2단계: 단계 2 (반나절): entities/*/api로 API 호출 집중화.
  • 3단계: 단계 3 (1일): DI 또는 context를 통해 httpClient를 주입 가능하게 만들어 테스트 및 스택 변경을 용이하게 함.

5. 🧹 Shared 레이어 순수성

💡 개념 정의

Shared 레이어는 도메인에 독립적인 UI 컴포넌트와 유틸을 포함하여 다른 slice에서 재사용 가능한 코드를 제공합니다. 도메인 로직이 들어가면 재사용성이 떨어집니다.

⚡ 중요성

Shared의 순수성은 새로운 프로젝트나 다양한 도메인으로 컴포넌트를 재사용할 때 유지보수 비용을 낮추고, 디자인 시스템 변경 시 수정 범위를 최소화시킵니다.

📊 현재 상황 분석

Shared 계층 자체는 비교적 순수하지만, 일부 컴포넌트의 displayName 또는 forwardRef 타입 정의가 반복적이므로 코드 스타일 정리가 필요합니다. 전반적으로 shared/ui는 잘 구성되어 있습니다.

📝 상세 피드백

개요 및 평가 요약:
components/index.tsx는 Button, Input, Card, Dialog 등 많은 UI primitives를 제공하고 있어 shared/ui 레이어로서 역할을 잘 하고 있습니다. 이 점은 디자인 시스템 교체(예: Tailwind → Chakra) 시 이 파일만 교체하면 되는 장점을 제공합니다.
주의 사항: shared 레이어 내에 도메인 로직(예: user-specific label 등)이 섞여있지 않은지 확인 필요. 현재 PR에서는 shared가 범용 컴포넌트로 잘 분리되어 있어 재사용성과 순수성이 확보되어 있습니다.

❌ 현재 구조 (AS-IS)

AS-IS: shared/ui (components/index.tsx)에는 Button, Card, Dialog 등이 정의되어있음. PostsManagerPage는 import { Button, Card, Select } from '../components' 형태로 사용.

✅ 권장 구조 (TO-BE)

TO-BE: shared/ui는 완전히 도메인 독립적으로 유지하고, 도메인별 Badge 같은 것은 features/user/ui에서 Badge를 조합하여 생성.
// shared/ui/Badge.tsx (도메인 없음)
// features/user/UserStatusBadge.tsx (도메인 로직 포함)

🔄 변경 시나리오별 영향도

  1. 디자인 시스템 변경: components/index.tsx에서 스타일 클래스만 대체하면 프로젝트 전반에 적용 가능(수정 파일 수: 1~2개).
  2. 도메인 요구(예: UserStatusBadge 도메인 의존): 도메인 로직은 features에서 구현하고 shared는 단순 Badge로 유지.

🚀 개선 단계

  • 1단계: 단계 1 (반나절): components/index.tsx에 있는 컴포넌트들의 prop-types/타입을 정리하고 문서화.
  • 2단계: 단계 2 (반나절): shared 레이어에 도메인 로직이 섞여있는지 스캔하고 분리.
  • 3단계: 단계 3 (1일): 디자인 시스템 교체 시나리오로 시도적 리팩토링(components 레이어만 변경) 수행.

6. 📐 추상화 레벨

💡 개념 정의

추상화는 복잡한 구현 세부사항(네트워크, DB, 라이브러리 특성)을 숨기고 재사용 가능한 인터페이스(함수, 훅, 클래스)만 노출하는 것입니다.

⚡ 중요성

높은 추상화는 기술 교체, 테스트, 재사용성을 용이하게 합니다. 특히 API나 상태관리 전략 변경 시 수정 범위를 줄여줍니다.

📊 현재 상황 분석

비즈니스 규칙(예: postsWithUsers 생성)은 페이지 내부에서 작성되어 재사용 불가. 또한 URL 업데이트 로직(updateURL)도 페이지 내부에 있어 재사용성이 없음.

📝 상세 피드백

개요 및 평가 요약:
비즈니스 로직과 기술적 세부사항(HTTP, 라우터 파라미터 처리, 로컬 UI 상태)이 PostsManagerPage 내에 섞여 있어 추상화 수준이 낮습니다. 핵심 비즈니스 개념(Posts, Comments, Users)이 코드에서 명확히 캡슐화되어 있지 않아 재사용 및 테스트가 어렵습니다.
권장 방향:

  • API 추상화(entities/*/api)
  • 비즈니스 훅(features/*/hooks)으로 도출
  • UI는 단순 표현(프레젠테이셔널 컴포넌트)으로 유지

❌ 현재 구조 (AS-IS)

AS-IS 예시:
const fetchPosts = () => { fetch('/api/posts') /* 후처리(유저 매칭) */ }
// 유저 매칭 로직도 이 함수 내부에 있음

✅ 권장 구조 (TO-BE)

TO-BE 예시:
// entities/post/model/postModel.ts
export const attachAuthors = (posts, users) => posts.map(p => ({ ...p, author: users.find(u => u.id === p.userId) }));

// pages/usePosts.ts
const { data } = useQuery(...)
const postsWithAuthors = useMemo(() => attachAuthors(data.posts, users), [data, users]);

🔄 변경 시나리오별 영향도

  1. 새로운 데이터 소스(서버 B) 통합: 추상화가 잘 되어있으면 postApi만 확장하면 되지만, 현재는 페이지 곳곳을 수정해야 함.
  2. 인증 방식 변경: API 호출마다 토큰 첨부 로직을 httpClient로 옮기면 쉽지만, 현재는 분산되어 있음.

🚀 개선 단계

  • 1단계: 단계 1 (반나절): 도메인 유틸(attachAuthors 등)을 entities/*/model로 추출.
  • 2단계: 단계 2 (1일): API abstraction과 비즈니스 훅으로 분리하여 컴포넌트는 뷰만 담당하도록 변경.
  • 3단계: 단계 3 (1일): 컴포넌트 경량화(프레젠테이셔널 / 컨테이너 패턴 적용).

7. 🧪 테스트 용이성

💡 개념 정의

테스트 용이성은 코드가 얼마나 독립적으로, 빠르게, 신뢰성 있게 테스트될 수 있는지를 나타냅니다. 순수 함수, 의존성 주입, 모듈화가 주요 요인입니다.

⚡ 중요성

테스트 가능한 구조는 회귀 버그를 줄이고 리팩토링 시 안정성을 보장합니다. 특히 서버 API 변경, UI 리팩토링 같은 변화에 대해 빠른 피드백을 제공합니다.

📊 현재 상황 분석

단위 테스트를 위해선 PostsManagerPage를 쪼개서 훅(데이터)과 프레젠테이셔널 컴포넌트(뷰)로 분리해야 합니다. 이렇게 하면 훅은 mock된 api로, 뷰는 props 주입으로 테스트 가능.

📝 상세 피드백

개요 및 평가 요약:
현재 구조는 테스트하기 어렵습니다. fetch가 컴포넌트에 분산되어 있고 많은 로컬 상태(useState)와 사이드 이펙트(useEffect)에 의존하고 있어 단위 테스트와 통합 테스트 작성 시 많은 mocking과 복잡한 셋업이 필요합니다.
권장 개선:

  • API 호출을 entities/*/api로 이동하면 api 레이어만 모킹하면 됨
  • 비즈니스 훅(usePosts 등)을 만들면 훅 단위 테스트가 가능
  • 프레젠테이셔널 컴포넌트로 분리하면 snapshot/unit 테스트 용이

❌ 현재 구조 (AS-IS)

AS-IS 예시: 컴포넌트 내부에서 fetch 호출  setState + analytics 트래킹  사이드 이펙트가 섞여 있음 -> 테스트하려면 네트워크/analytics 모두 mocking 해야 .

✅ 권장 구조 (TO-BE)

TO-BE 예시: usePosts 훅을 만들고 postApi를 주입하여  단위로 mock postApi를 이용한 테스트 작성.
const { result } = renderHook(() => usePosts(), { wrapper: QueryClientProvider });

🔄 변경 시나리오별 영향도

  1. 새 외부 API 연동: api 레이어를 모킹하면 훅/컨테이너 테스트만으로 검증 가능.
  2. 복잡한 비즈니스 로직 추가: 도메인 로직을 순수 함수로 추출하면 단위 테스트로 빠르게 검증 가능.

🚀 개선 단계

  • 1단계: 단계 1 (반나절): API 추상화 및 postApi 모킹 가능한 인터페이스로 전환.
  • 2단계: 단계 2 (1일): 훅/프레젠테이셔널 분리 후 훅 단위 테스트, UI 스냅샷/유닛 테스트 추가.
  • 3단계: 단계 3 (1일): CI에 테스트 실행 파이프라인 추가.

8. ⚛️ 현대적 React 패턴

💡 개념 정의

현대 React 패턴은 선언적 비동기 처리(Suspense), 에러 경계(ErrorBoundary), 재사용 가능한 커스텀 훅 등을 활용해 UI와 로직의 관심사를 분리합니다.

⚡ 중요성

이 패턴들은 로딩/에러 상태 처리를 일관되게 만들고, UI가 복잡해졌을 때도 컴포넌트 책임을 명확히 유지하도록 돕습니다.

📊 현재 상황 분석

로딩 처리(setLoading)와 에러 로깅(console.error)가 분산되어 있어 일관된 사용자 경험 제공이 어렵습니다. ErrorBoundary나 Suspense 미사용으로 예외 상황에 대한 복원력도 낮습니다.

📝 상세 피드백

개요 및 평가 요약:
현재 코드에서는 Suspense, ErrorBoundary, React Query(데이터 패칭의 선언적 패턴) 등이 활용되지 않았습니다. 대신 useEffect/useState와 명령형 패턴을 사용하고 있어 로딩/에러 처리가 분산되어 있습니다.
권장 사항:

  • TanStack Query + Suspense 조합(가능한 경우)으로 선언적 로딩 처리
  • ErrorBoundary로 페이지/위젯 단위 에러 격리
  • 비즈니스 로직은 커스텀 훅으로 분리하여 컴포넌트는 단순 렌더링만 담당

❌ 현재 구조 (AS-IS)

AS-IS: setLoading(true) / setLoading(false) 패턴이 여러 fetch에 반복되어 있음.
AS-IS : fetchPosts() { setLoading(true); fetch(...).finally(() => setLoading(false)); }

✅ 권장 구조 (TO-BE)

TO-BE: usePosts  + Suspense
<Suspense fallback={<Spinner/>}>
  <PostsList />
</Suspense>
// PostsList 내부는 데이터가 준비된 상태에서 렌더링됨

🔄 변경 시나리오별 영향도

  1. 로딩 UX 변경: 현재는 각 로딩 플래그를 변경해야 하지만, Suspense로 전환하면 상위 fallback 컴포넌트만 교체하면 됨.
  2. 에러 처리 중앙화: ErrorBoundary로 래핑하면 특정 컴포넌트의 에러가 전체 앱을 중단시키지 않음.

🚀 개선 단계

  • 1단계: 단계 1 (반나절): TanStack Query 훅 적용으로 로딩/에러 상태 관리를 중앙화.
  • 2단계: 단계 2 (반나절): 페이지 레벨에 Suspense와 ErrorBoundary 적용(점진적 적용 권장).
  • 3단계: 단계 3 (반나절): 커스텀 훅으로 비즈니스 로직 분리.

9. 🔧 확장성

💡 개념 정의

확장성(Extensibility)은 새로운 요구사항을 도입할 때 기존 구조를 최소한으로 변경하고 쉽게 통합할 수 있는 능력입니다.

⚡ 중요성

프로덕트 성장과 요구사항 변화에 빠르게 대응하려면 확장성이 중요합니다. 초기 설계가 확장성을 뒷받침하면 장기 유지비용이 감소합니다.

📊 현재 상황 분석

예: 실시간 댓글 동기화 기능을 추가할 때 socket 연결과 메시지 수신 핸들러를 페이지에 붙이면 테스트/유지보수가 어려움. 대신 useRealtimeComments 훅으로 분리하면 영향 범위를 줄일 수 있음.

📝 상세 피드백

개요 및 평가 요약:
현재 구조는 새로운 기능(다국어, 실시간 소켓 연동, 오프라인 모드 등)을 추가할 때 확장성이 떨어질 가능성이 큽니다. 많은 기능이 단일 페이지 파일에 집중되어 있어 cross-cutting concern(예: i18n, A/B 테스트) 적용 시 넓은 수정 범위를 초래합니다.
권장 접근법:

  • cross-cutting 기능(i18n, feature flags)은 App 레벨 또는 shared 레이어에 도입
  • 실시간 기능은 features/posts에서 소켓 훅으로 분리
  • 오프라인은 캐싱 전략을 담당하는 레이어(entities/api / service worker)에서 처리

❌ 현재 구조 (AS-IS)

AS-IS: PostsManagerPage에 하드코딩된 텍스트(: '로딩 중...', '이전', '다음') 다수의 UI 문자열이 포함되어 있음.

✅ 권장 구조 (TO-BE)

TO-BE: i18n 적용 
<Text>{t('pagination.prev')}</Text>
// i18n provider로 전체 앱 래핑

🔄 변경 시나리오별 영향도

  1. A/B 테스트 도입: feature-flag를 통해 컴포넌트별 분기 처리를 하면 Pages는 훅/컴포넌트 교체만으로 대응 가능.
  2. 다국어(i18n) 도입: 텍스트 리터럴을 컴포넌트에서 제거하고 i18n 키를 사용하면 전체 검색/교체가 쉬워짐.

🚀 개선 단계

  • 1단계: 단계 1 (반나절): 문자열 리터럴을 i18n 키로 추출(필요 시 간단한 i18n 라이브러리 도입).
  • 2단계: 단계 2 (1일): 실시간/오프라인 등의 cross-cutting concern을 훅/서비스로 추상화.
  • 3단계: 단계 3 (1-2일): Feature Flags 또는 Config 기반 환경 분기 도입.

10. 📏 코드 일관성

💡 개념 정의

코드 일관성은 네이밍, 파일명, import/export 패턴 및 스타일 가이드가 일관되게 적용되어 있는 상태를 말합니다. 일관성은 가독성과 협업 효율을 높입니다.

⚡ 중요성

일관된 스타일은 새로운 개발자 온보딩을 빠르게 하고, 자동화 도구(빌드/분석)가 안정적으로 동작하게 합니다. 또한 불필요한 충돌을 줄입니다.

📊 현재 상황 분석

components/index.tsx는 스타일 정리 및 displayName 통일이 잘 되어있음. 반면 PostsManagerPage.tsx는 포맷팅/문법 문제뿐 아니라 중복으로 인해 export가 여러 번 선언되는 등 치명적 상태입니다.

📝 상세 피드백

개요 및 평가 요약:
PR에는 스타일 정리(따옴표 통일, 세미콜론 추가 등)가 전반적으로 적용되어 있어 코드 일관성 측면에서는 개선이 보입니다. 다만 PostsManagerPage.tsx에 심각한 문제(파일 끝부분의 대량 중복 복붙/잔여 텍스트)가 포함되어 있어 문법 오류 및 유지보수 위험이 큽니다.
구체적 이슈:

  • 파일 말미에 중복된 블록(electedUser?.age} ... export default PostsManager)이 여러 번 붙어 있어 빌드 오류를 유발하거나 예기치 않은 동작을 초래함.
  • Import/Export 패턴: 대부분 named/default export 일관되게 사용되고 있으나 components/index.tsx는 많은 named exports로 잘 구성되어 있음.
    권장 조치:
  • PostsManagerPage.tsx의 중복/잡다한 텍스트 제거(즉시 수정 필요)
  • ESLint/Prettier 설정으로 코드 스타일 및 빌드 에러 예방

❌ 현재 구조 (AS-IS)

문제 예시(파일 말미 발췌):
... export default PostsManager
electedUser?.age}
  </p>
  <p> <strong>이메일:</strong> {selectedUser?.email}</p>
... // 동일 블록 여러 번 반복

✅ 권장 구조 (TO-BE)

정상 예시:
export default PostsManager;
// 파일 끝에 중복 텍스트/코드 없음

🔄 변경 시나리오별 영향도

  1. 자동화 도구 도입(ESLint/Prettier): 코드 스타일 문제는 PR 단계에서 자동으로 잡을 수 있음.
  2. 파일 중복 제거: 즉시 제거하면 빌드가 정상화되고 코드 리뷰가 용이해짐.

🚀 개선 단계

  • 1단계: 긴급(즉시): PostsManagerPage.tsx 말미의 중복 블록과 잔여 텍스트를 제거하여 문법/빌드 오류 해결.
  • 2단계: 단기(반나절): ESLint/Prettier 설정 추가 및 pre-commit 훅(Husky + lint-staged) 적용으로 스타일/문법 자동 검증.
  • 3단계: 중기(1일): 코드 리뷰 체크리스트에 스타일/빌드 검증 항목 추가.

🎯 일관성 체크포인트

Import/Export 패턴

  • components/index.tsx는 named exports를 일관되게 사용 중이라 양호함

변수명 규칙

  • 전반적으로 camelCase 사용, 큰 이슈는 발견되지 않음

코드 스타일

  • PostsManagerPage.tsx 말미의 중복 및 문법 오류(잉여 텍스트/중복 export) — 즉시 제거 필요
  • ESLint/Prettier 자동화 부재(또는 적용 누락) 가능성

11. 🗃️ 상태 관리

💡 개념 정의

데이터 흐름 및 상태 관리는 전역 서버 상태(server state), 전역 클라이언트 상태(global UI state), 지역 컴포넌트 상태(local state)를 명확히 분리하는 것을 말합니다.

⚡ 중요성

서버 상태와 클라이언트 상태를 명확히 분리하면 캐싱, 리패칭, 낙관적 업데이트, 그리고 스케일링(실시간/오프라인) 시 복잡도를 관리하기 쉬워집니다.

📊 현재 상황 분석

다음 개선을 권장합니다:

  • useQuery 기반 훅으로 posts/comments/tags 관리
  • 다이얼로그 등 UI 토글은 local state 또는 feature-level store로 유지
  • URL 파라미터(updateURL 함수)는 useEffect 훅 안으로 옮겨 캡슐화 또는 useUrlSync 훅으로 추출

📝 상세 피드백

개요 및 평가 요약:
서버 상태와 클라이언트 UI 상태가 PostsManagerPage.tsx에 혼재되어 있습니다. 예: posts, comments, tags(서버 상태)와 showAddDialog, selectedPost(로컬 UI 상태)이 동일 레이어에 존재합니다. 전역 상태관리 도구(예: Jotai, Zustand 등)는 사용되지 않았고, TanStack Query로의 전환도 미적용 상태입니다.
권장 접근:

  • 서버 상태: TanStack Query로 중앙화
  • 클라이언트 UI 상태: feature/local state 또는 lightweight store(Jotai/Zustand)로 분리
  • URL 동기화는 별도의 훅(useUrlSync)으로 분리

❌ 현재 구조 (AS-IS)

AS-IS 예시:
const [posts, setPosts] = useState([]);
const fetchPosts = () => { fetch(...).then(data => setPosts(data.posts)) }
// 검색/필터링/페이징이 모두 로컬 상태와 섞여 구현됨

✅ 권장 구조 (TO-BE)

TO-BE 예시:
const { data: posts } = usePosts({ skip, limit });
const { mutate: addPost } = useAddPostMutation();
// UI는 훅만 사용하여 렌더링

🔄 변경 시나리오별 영향도

  1. 실시간 동기화: 서버 상태를 TanStack Query로 관리하면 websocket 이벤트로 invalidateQueries만 호출하면 되므로 적용 용이.
  2. 오프라인 모드: 캐싱 전략과 service worker 연동으로 오프라인 UX 개선 가능.

🚀 개선 단계

  • 1단계: 단계 1 (반나절): posts/tags/comments 관련 fetch를 postApi/commentApi로 이동.
  • 2단계: 단계 2 (1일): TanStack Query 훅(usePosts/useComments/useTags)로 전환.
  • 3단계: 단계 3 (반나절): 로컬 UI 상태(다이얼로그 등)는 useState 또는 작은 상태관리 라이브러리로 유지하여 역할 분리.

🤔 질문과 답변

질문: '리뷰 받고 싶은 내용이나 궁금한 것' 항목이 비어 있습니다. 대신 아래 사항들을 중심으로 권장드립니다:

  1. 현재 코드의 최우선 수정은 무엇인가요?
  • 답: PostsManagerPage.tsx 말미의 중복/잡다한 텍스트 제거(빌드 오류 우려)와 API 호출 추출(postApi 생성)입니다. 우선 빌드가 통과하도록 문법 오류를 제거하고, fetch 호출을 한 곳으로 모으는 작업을 권장합니다.
  1. TanStack Query를 어떻게 도입하면 좋을까요?
  • 답: posts/tags/comments 각 리소스별 entities//api에 순수 CRUD 함수를 만들고, features//hooks에서 useQuery/useMutation 래핑을 만든 뒤 페이지는 훅을 통해 데이터만 소비하도록 리팩터하세요. 낙관적 업데이트는 useMutation의 onMutate/onError/onSettled로 구현하면 됩니다.
  1. 적용 우선순위와 예상 시간은?
  • 답: (1) 긴급: 문법 오류/중복 제거(0.5일). (2) 단기: API 추출(postApi/commentApi)(0.51일). (3) 중기: TanStack Query 훅 전환(12일). (4) 장기: FSD 폴더 재배치 및 테스트 추가(2~4일).

🎯 셀프 회고 & 제안

작성자의 회고(출장으로 과제 미진행, FSD 개념 학습 등)에 대한 피드백 및 확장 질문:
인사이트: FSD의 핵심 개념을 이해하신 점은 훌륭합니다. 현재 PR에서 문제는 '이해'는 되어 있으나 '적용' 단계에서 미완성(특히 TanStack Query와 관심사 분리 미적용)이라는 점입니다. 학습 곡선 상의 자연스러운 단계로 보입니다.
생각을 더 확장해볼 질문들:

  • '내가 FSD에서 가장 먼저 정리하고 싶은 slice는 무엇인가요?' (예: posts, comments, users 중 우선순위 결정)
  • '현재 한 파일에 있는 책임을 나눈다면 첫 번째로 어떤 함수를 추출하겠는가?' (fetch -> api, highlightText -> util 등)
  • '향후 팀 규모가 커졌을 때, 누가 어느 slice를 담당할지 소유권을 어떻게 나눌 것인가?'
    추가 제안: 과제 1~2단계(문법/중복 제거, API 추출)를 먼저 수행하면서 작은 성공사례를 만들고(예: usePosts 훅 하나 완성), 점차 다른 기능으로 확장해 보세요. 이렇게 하면 학습 부담을 줄이며 FSD 적용 경험을 쌓을 수 있습니다.

추가 논의가 필요한 부분이 있다면 언제든 코멘트로 남겨주세요!

코드 리뷰를 통해 더 나은 아키텍처로 발전해 나가는 과정이 즐거웠습니다. 🚀

이 피드백이 도움이 되었다면 👍 를 눌러주세요!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants