Skip to content

[9팀 박상수] Chapter 2-3. 관심사 분리와 폴더구조#50

Open
parksangsoo wants to merge 4 commits intohanghae-plus:mainfrom
parksangsoo:main
Open

[9팀 박상수] Chapter 2-3. 관심사 분리와 폴더구조#50
parksangsoo wants to merge 4 commits intohanghae-plus:mainfrom
parksangsoo:main

Conversation

@parksangsoo
Copy link

과제 체크포인트

기본과제

목표 : 전역상태관리를 이용한 적절한 분리와 계층에 대한 이해를 통한 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가 정상적으로 작동하는가?

최종과제

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

과제 셀프회고

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

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

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

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

챕터 셀프회고

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

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

  • 더티코드를 접했을 때 어떤 기분이었나요? ^^; 클린코드의 중요성, 읽기 좋은 코드란 무엇인지, 유지보수하기 쉬운 코드란 무엇인지에 대한 생각을 공유해주세요

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

  • 거대한 단일 컴포넌트를 봤을때의 느낌! 처음엔 막막했던 상태관리, 디자인 패턴이라는 말이 어렵게만 느껴졌던 시절, 순수함수로 분리하면서 "아하!"했던 순간, 컴포넌트가 독립적이 되어가는 과정에서의 깨달음을 들려주세요

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

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

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

@parksangsoo parksangsoo changed the title ㄴㅇㄴ [9팀 박상수] Chapter 2-3. 관심사 분리와 폴더구조 Aug 14, 2025
Copy link

@susmisc14 susmisc14 left a comment

Choose a reason for hiding this comment

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

안녕하세요! AI 코드 리뷰어입니다. 🤖
게시물 관리자 리팩터링 PR(게시물 FSD 분리 + Jotai 도입)에 대한 리뷰를 완료했어요. 함께 코드를 더 발전시켜 볼까요?

Note

✨ 이번 PR 한 줄 요약
FSD 폴더 구조와 Jotai 전역 상태 도입, UI 컴포넌트 분리 시도가 잘 보이지만 — 잘못된 import/경로, API 함수/응답 처리 불일치와 타입/네이밍 문제로 런타임 에러·버그가 발생할 가능성이 큽니다. 또한 심화 과제(루틴: TanStack Query 도입)는 아직 반영되지 않았습니다.


🎯 핵심 리뷰 요약

가장 중요한 개선 포인트부터 빠르게 확인해 보세요.

  • P1: 잘못된 import 경로와 중복/충돌하는 훅/함수명(usePost 등) → 빌드/런타임 에러 유발
  • P1: API 함수(공통 api)와 훅에서의 응답 처리 불일치(Response vs parsed JSON) → 데이터 사용 불가
  • P1: features <-> entities 간 파일 위치/임포트 불일치 (예: CommentListDialog 경로) → 모듈 경로 오류
  • P2: atoms/상태의 타입이 없음(타입 안정성 부족) 및 Jotai 전역 atom 설계 검토 필요(응집도/결합도)
  • P2: TanStack Query 미도입 — 심화 요구사항 미충족, 서버 상태 관리 전략 부재
  • P3: FSD 적용 시 일부 모듈(태그/포스트 훅 등) 명명/중복으로 아키텍처 일관성 저하

🔍 상세 리뷰

1. Import/경로 및 네이밍 충돌 문제 (P1)

  • 👍 좋은 점:
    • components → widgets/ui로 이동 등 FSD 구조 반영 시도는 적절합니다.
  • 💡 개선 제안:
    • PostsManager에서 import한 컴포넌트 경로와 실제 파일 위치가 불일치합니다.
      • 예: PostsManager.tsx가 import { CommentListDialog } from "../entities/comment/ui/CommentListDialog" 를 사용하지만 실제 파일은 src/features/post/comment/ui/CommentListDialog.tsx 입니다.
    • 훅/유틸 함수의 이름 충돌을 제거하세요. (예: features/post/tag/hooks/useTag.ts 에서 export한 훅 이름이 usePost로 되어 있음 -> 의미에 맞는 고유명 사용: useTagFeature 등)
  • 🤔 이렇게 제안하는 이유:
    • 잘못된 경로는 빌드타임/런타임 에러를 바로 발생시키며, 같은 이름의 훅이 여러곳에 존재하면 유지보수와 자동 import에 혼란을 줍니다.
  • 💻 코드 예시 (AS-IS vs TO-BE)
    AS-IS
    // src/features/post/tag/hooks/useTag.ts (현재)
    export const usePost = () => { ... } // 이름 충돌
    
    TO-BE
    export const useTagFeature = () => { ... }
    // 그리고 PostsManager에서는
    import { CommentListDialog } from "../features/post/comment/ui/CommentListDialog"
    

2. API 함수와 훅의 응답 처리 불일치 — Response vs JSON (P1)

  • 👍 좋은 점:
    • API 함수들을 각 도메인별 파일로 분리하려 한 시도는 좋습니다.
  • 💡 개선 제안:
    • API 함수는 가능한 한 일관된 계약을 가져야 합니다. (리턴: 항상 parsed JSON 또는 에러 throw)
    • 현재 일부 api는 Response 객체를 반환(getPostApi 등)하고, 훅에서는 .json()을 기대하거나 반대로 .json()을 이미 하는 경우가 섞여 있습니다.
    • addPostApi/updatePostApi 등은 parsed JSON을 반환하도록 수정하세요.
  • 🤔 이렇게 제안하는 이유:
    • 호출부가 .json()을 기대하느냐 아니냐에 따라 코드가 깨지고, 타입 안정성도 떨어집니다.
  • 💻 코드 예시 (AS-IS vs TO-BE)
    AS-IS (post-api.ts)
    export const getPostApi = async (limit, skip) => {
      const response = await fetch(`/api/posts?limit=${limit}&skip=${skip}`)
      if (!response.ok) throw new Error(...)
      return response
    }
    
    TO-BE
    export const getPostApi = async (limit, skip) => {
      const res = await fetch(`/api/posts?limit=${limit}&skip=${skip}`)
      if (!res.ok) throw new Error(...)
      return res.json() // 항상 parsed JSON 반환
    }
    // 또는
    // return { posts: ..., total: ... } 형태를 보장
    

3. usePost 훅(entities)와 features 훅의 충돌 및 잘못된 의존 (P1/P2)

  • 문제 포인트:
    • src/entities/post/hooks/usePost.ts에서 import { getTagApi, getPostsByTagApi, ... } from "../api/post-api" 를 하고 있지만, 해당 함수들은 post-api에 정의되어 있지 않습니다(태그 API는 features/.../tag-api.ts에 정의됨). 또한 searchPostApi 시그니처 불일치 등.
    • addPost/updatePost 사용 시 API의 반환값을 parsed JSON으로 기대하는데 실제 post-api는 Response를 반환하는 상태(또는 반대) — 앞서 언급한 응답 불일치와 결합되어 에러 발생.
  • 권장 개선:
    • 훅은 도메인 별로 명확히 하나의 책임만 갖도록 하세요(서버 통신과 로컬 상태 업데이트의 책임 분리).
    • API 호출은 domain/api/* 에서만 하고, 훅에서는 해당 함수의 반환 계약에 의존.
    • 함수명을 명확히 해 충돌 방지(usePostEntity, usePostFeature 등).
  • 이유:
    • 응집도(Cohesion)를 높이고 결합도(Coupling)를 낮추면 변경시 파급 범위가 줄어듭니다.

4. 타입 안정성 및 Jotai atom 정의 개선 (P2)

  • 문제 포인트:
    • atoms가 대부분 any 타입으로 생성되어 타입 안정성이 없습니다. (예: postsAtom = atom([]))
    • selectedUserAtom = atom<SelectedUser>(null) 은 좋지만 다른 atom들은 제너릭 타입이 없음.
  • 개선 제안:
    • 각 atom에 제네릭 타입을 명확히 선언하세요.
      • postsAtom = atom<Post[]>([])
      • totalPostsAtom = atom<number>(0)
    • 가능한 경우 atomWithStorage, atomFamily 등을 사용해 명확히 분리.
  • 근거(기술적 배경):
    • 타입이 명확하면 자동완성·리팩토링·버그 예방에 유리합니다.

5. FSD(Feature-Sliced Design) 적용 관찰 & 개선(아키텍처) (P2/P3)

  • 👍 좋은 점:

    • entities/post, features/post/comment, widgets/ui, shared/* 로 분리하려는 시도는 FSD 원칙에 맞습니다.
  • 개선 제안(AS-IS vs TO-BE)

    • AS-IS: 파일들이 대체로 분리되어 있으나 일부 파일이 잘못된 위치에 있거나 import 경로가 꼬여 있음. 훅과 컴포넌트의 책임이 혼재됨.
    • TO-BE: 아래처럼 좀 더 엄격한 구조와 네이밍 규칙을 추천합니다.

    제안 폴더 구조 (알파벳 정렬 포함)

    src/
    ├─ base/ (shared lowest-level lib)
    ├─ entities/
    │  ├─ post/
    │  │  ├─ api/
    │  │  ├─ model/
    │  │  ├─ ui/        // 단순 presentation 컴포넌트
    │  │  └─ hooks/     // entity-level hooks (usePostEntity)
    │  └─ user/
    ├─ features/
    │  └─ post/\
    │     ├─ comment/
    │     │  ├─ api/
    │     │  ├─ ui/
    │     │  └─ hooks/  // feature-level logic (useCommentFeature)
    │     └─ tag/
    ├─ modules/ (widgets)
    ├─ pages/
    └─ shared/
    
  • 이유:

    • entities는 데이터(타입·model) 중심, features는 사용자 행동(유스케이스) 중심으로 나누면 응집도가 높아집니다.

6. TanStack Query(심화과제) — 미적용 상태 & 도입 제안 (P2)

  • 현재 상태:
    • PR은 Jotai를 클라이언트 상태로 잘 도입했으나, TanStack Query는 사용되지 않았습니다 → 심화 체크포인트 미충족
  • 권장 접근:
    • 서버상태(게시물/댓글/유저/태그) 관리는 TanStack Query로 이관하세요. 로컬 UI 상태(다이얼로그 열림/선택된 아이템 등)는 Jotai로 유지.
    • Query Key Factory 패턴 도입으로 쿼리 키를 중앙화하면 캐시 관리가 쉬워집니다.
  • 예시: Query Key Factory + useQuery + useMutation (요약)
    // src/shared/lib/queryKeys.ts
    export const postsKeys = {
      all: ['posts'] as const,
      lists: (filters) => [...postsKeys.all, 'list', filters] as const,
      details: (id) => [...postsKeys.all, 'detail', id] as const,
    }
    
    // usePosts.ts (hook)
    const usePosts = (filters) => {
      return useQuery(postsKeys.lists(filters), () => api.getPosts(filters))
    }
    
    // add post optimistic update
    const useAddPost = () => {
      const qc = useQueryClient()
      return useMutation(api.addPost, {
        onMutate: async (newPost) => {
          await qc.cancelQueries(postsKeys.all)
          const previous = qc.getQueryData(postsKeys.all)
          qc.setQueryData(postsKeys.all, (old: any) => ({...old, posts: [newPost, ...old.posts]}))
          return { previous }
        },
        onError: (err, newPost, ctx) => qc.setQueryData(postsKeys.all, ctx.previous),
        onSettled: () => qc.invalidateQueries(postsKeys.all),
      })
    }
    
  • 이유:
    • TanStack Query를 통해 캐시/리페치/낙관적 업데이트 관리가 쉬워지고 서버 상태가 분명해집니다.

7. DummyJson 라이브러리 한계 관련 조언 (P2)

  • 문제:
    • DummyJson(예: /api/posts/add 등)이 실제로 서버에 영구 반영하지 않을 경우, 지금 같은 fetch 기반 추가/수정/삭제는 클라이언트 상태만 변경하는 임시 로직이 필요합니다.
  • 권장 대안:
    • 대안 1: TanStack Query의 캐시 직접 수정 (위 optimistic update 예시). 서버 응답이 변하지 않더라도 클라이언트에서 일관된 상태 유지 가능.
    • 대안 2: MSW(Mock Service Worker) 도입 — 로컬에서 CRUD 동작을 모킹하고, 테스트·개발에서 실제 백엔드처럼 동작하도록 구성.
  • 예시: MSW 기본 핸들러
    // handlers.ts
    rest.post('/api/posts/add', (req, res, ctx) => {
      const newPost = { id: ++idCounter, ...req.body }
      postsDB.unshift(newPost)
      return res(ctx.status(201), ctx.json(newPost))
    })
    
  • 이유:
    • 테스트·개발 환경에서 클라이언트 로직(낙관적 업데이트, 에러 복구 등)을 더 현실적으로 검증할 수 있습니다.

8. 코드 일관성(스타일) & 라이브러리 버전 확인 (P3)

  • 네이밍/컨벤션:
    • import 순서/형식 통일: 외부 라이브러리 -> 절대/alias import -> 상대 import 순으로 정렬 권장.
    • 컴포넌트/파일명 규칙은 PascalCase로 유지(잘 지켜짐).
  • 스타일 가이드:
    • Airbnb 스타일과 Toss Frontend Fundamentals에 부합하도록 일부 화살표 함수의 반환 타입 명시, any 지양 권장.
  • 라이브러리 버전:
    • React ^19.1.1, Jotai ^2.13.1 사용중 — 현재 코드에서 사용하는 Jotai atom API는 호환성 문제 없음. (React 19 관련 deprecated API 없음 특별한 주의사항 없음)
    • TanStack Query 도입 예정이라면 최신 버전에 맞춰 useQueryClient/Mutation API 사용하세요.

🗺️ 전체 개선 로드맵 (우선순위 가이드)

  • P1 (Must-Fix) 🚨:
    1. 잘못된 import 경로 수정 (PostsManager 및 기타 파일) — 빌드 실패 방지.
    2. API 함수의 반환 계약 통일 (Response vs JSON) 및 훅에서의 사용 일치화.
    3. 중복/충돌 훅명(usePost 등) 고유명으로 변경.
  • P2 (Recommended) ⚠️:
    1. TanStack Query 도입 및 Query Key Factory로 서버 상태 관리 전환.
    2. atoms에 명시적인 타입 적용 및 일부 atom 책임 재검토.
    3. DummyJson 한계에 대한 대응 (낙관적 업데이트 또는 MSW).
  • P3 (Optional) 💡:
    1. 폴더 구조 정리(위 TO-BE 구조) 및 문서화.
    2. 코드 스타일 정리(ESLint/Prettier 규칙 강제).
    3. 테스트(단위/통합) 추가 — MSW 활용 권장.

🤔 한 걸음 더: 코드와 의도 돌아보기

PR 본문에서 의도(전역 상태 관리, FSD 적용)가 잘 드러났습니다. 몇 가지 스스로 질문해보세요.

  • 목표와 구현의 연결고리: “전역 상태 분리는 entities와 features 중 어디에 넣어야 했는가?” — posts의 서버 상태는 features(서버유지)로, UI 상태(다이얼로그 등)는 entities나 shared로 나눌 수 있는지 검토해보셨나요?
  • 숨겨진 의도 파악: Post 관련 api 함수를 entities/post/api에 둔 이유는 무엇인가요? tag/ comment api는 features 쪽에 남겨둔 이유가 있었나요?
  • 스스로에게 던지는 질문:
    • 이번 PR에서 가장 자랑하고 싶은 변경은 무엇이며 왜인가요?
    • 훅(usePost 등)의 추상화 수준은 적절한가요? (하나의 훅에 너무 많은 책임이 있진 않나요?)
    • Jotai와 TanStack Query의 책임(클라이언트 상태 vs 서버 상태) 분리가 명확한가요?
    • 동료가 코드를 볼 때 "왜 getPostApi가 Response를 반환하는가?" 같은 의문을 갖지 않도록 API 계약을 문서화했나요?

📚 참고 자료


이번 리뷰가 코드 품질 향상과 아키텍처 정립에 도움이 되었으면 합니다. 다음주도 화이팅 하세요! ✨

끝.

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