lieblichoi 님의 상세페이지[9팀 최재환] Chapter 2-3. 관심사 분리와 폴더구조

과제 체크포인트

https://lieblichoi.github.io/front_6th_chapter2-3/

기본과제

목표 : 전역상태관리를 이용한 적절한 분리와 계층에 대한 이해를 통한 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가 그냥 폴더 구조 바꾸는 거라고 생각했는데,, 과제를 하면서 드는 생각은 관심사를 어떻게 분리하고 조합할 것인가에 대한 고민 그 자체라고 해도 되겠다 싶었습니다.

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

왜 이 코드가 이 위치에 있어야 하는가를 스스로 물으면서 각 코드의 적절한 위치를 찾으려고 노력했습니다. 그리고 결합도를 낮추는데에도 많이 신경 썼습니다. 타입 안전성도 많이 고려했습니다.

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

Feature의 적절한 크기가 고민입니다. 예로는 지금 comment-manage가 댓글의 모든 CRUD를 담당하고 있는데, 이걸 comment-create, comment-edit, comment-list로 더 세분화해야 할지 판단이 잘 안섭니다. 너무 세분화하면 보일러플레이트가 늘어날 것 같고,, 너무 크면 단일 책임 원칙을 위반할 것 같고,,

서버 상태와 클라이언트 상태의 경계도 애매한 부분이 있습니다. 예를 들어 정렬 상태(sortBy, sortOrder)는 URL과 동기화되어야 하기 떄문에 클라이언트 상태인데 그와 동시에 서버 요청에도 영향을 줍니다. 이런 상태를 어떻게 관리해야 할지 고민입니다.

테스트 전략도 구상하기 쉽지않았습닏. FSD 구조에서 어떤 계층을 어떻게 테스트해야 효과적일지, 특히 낙관적 업데이트 같은 복잡한 로직을 어떻게 테스트할지 감이 잡히지 않습니다.

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

현재 프로젝트의 각 feature들이 이미 독립ㅈㄱ이라 이걸 별도의 앱으로 분리하는 것도 가능해 보이는데 post-management, user-profile, analytics 같은 도메인별 앱으로 분리하고 shared-ui, shared-types 패키지를 공유하는 구조를 시도해보고 싶습니다.

성능 최적화에도 장점이 있어보입니다. 각 feature가 독립적이니 코드 스플리팅을 적용하기도 좋고 widgets의 메모이제이션도 더 효과적으로 할 수 있을 것 같습니다.

현재 구조에 대한 추가 분석

기본적인 계층 구조(app → pages → widgets → features → entities → shared)는 잘 지켜지고 있는 것 같습니다만 몇 가지 아쉬운 부분들이 있습니다.

중복 계층 문제

  • src/api/src/entities/*/api/가 동시에 존재함
  • src/components/posts/는 완전히 미사용 상태
  • 같은 API 함수가 두 곳에 있는 경우 어디껄 써야하는지 헷갈림

hooks 위치 문제

  • usePosts 훅이 hooks/ 폴더에 있는데, 이게 실제로는 PostsManagerPage에 특화된 로직이라 FSD 관점에서 보면 pages나 widgets 계층에 있는 게 맞을 것 같습니다.

shared 계층 과부하

  • shared/ui/index.tsx가 길고 Button부터 Dialog까지 모든 UI 컴포넌트가 한 파일에 몰려있습니다. 아토믹 디자인 패턴의 atoms/molecules/organisms 단위로 나누면 좋을 것 같습니다.

복잡도 문제

  • PostsManagerPage가 7개 feature + 3개 widget을 관리하고 있어 좀 복잡합니다.
  • comment-manage feature는 댓글의 모든 CRUD를 담당하고 있는데 이것도 더 세분화할 수 있을 것 같습니ㄴ다.

폴더 구조 시각화

src/
├── app/                    # App Layer (전역 설정)
│   └── providers/
├── pages/                  # Pages Layer (페이지 조합)
│   └── PostsManagerPage.tsx
├── widgets/                # Widgets Layer (독립 UI 블록)
│   ├── pagination/
│   ├── post-filters/
│   └── post-list/
├── features/               # Features Layer (비즈니스 기능)
│   ├── comment-manage/
│   ├── post-create/
│   ├── post-delete/
│   ├── post-detail/
│   ├── post-edit/
│   ├── post-reactions/
│   └── user-detail/
├── entities/               # Entities Layer (도메인 모델)
│   ├── comment/
│   ├── post/
│   └── user/
├── shared/                 # Shared Layer (공통 자원)
│   ├── api/
│   ├── lib/
│   └── ui/
├── hooks/                  # Legacy Layer (FSD 외부)
├── components/             # Legacy Layer (미사용)
├── api/                    # 중복 Layer
└── types/                  # 중복 Layer
FSD 계층현재 위치상태비고
Appapp/정상Provider 설정
Pagespages/정상단일 페이지
Widgetswidgets/정상3개 위젯
Featuresfeatures/정상7개 기능
Entitiesentities/정상3개 엔티티
Sharedshared/과부하UI/API/LIB 혼재

챕터 셀프회고

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

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

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

가장 큰 문제는 한눈에 보이지 않는다.였습니다. 하나의 기능을 수정하려면 전체 코드를 다 이해해야한ㄴ게 필수이기도 하고 작은 변경도 예상치 못한 사이드이펙트로 이어질 수 있기에 코드의 구조를 빠르게 이해하는게 관건이었습니다.

리팩토링 후의 코드는 일단은 이름을 보면 역할을 알 수 있는 상태 정도는 되었습니다. usePostCreate, CommentDialog, PostsTable 같은 이름들이 명확해서 코드 읽기가 보다 수월해졌습니다.

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

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

일단 어디서부터 손을 대야 할까라는 막막함이 컸습인다. 모든 게 얽혀있다보니 하나를 건드리면 겉잡을 수 없을 거 ㅅ 같은 두려움이 제일 컸습니다.

아하했던 순간은,, entities/post/model/adapters.ts를 만들 때였는데 캐시 조작 로직을 순수 함수로 분리해보니 편해졌습니다.

export const applyInsertTop = (data: PostsApiResponse, post: Post): PostsApiResponse => ({
  ...data,
  posts: [post, ...data.posts],
  total: (data.total ?? 0) + 1,
})

이 함수는 입력이 같으면 항상 같은 출력을 보장하고, 원본을 변경하지 않으며, 테스트하기 쉽습니다(AI 짱짱맨) 복잡한 상태 변화를 단순한 함수로 표현할 수 있다는 게 좋았던 것 같습니다.

전역상태 관리에서도 어떤 상태를 전역에 둘 것인가가 더 중요한 문제였습니다. 토스트처럼 여러 곳에서 필요한 것만 전역에 두고 feature 내부 상태는 캡슐화하는 식으로 나누어 생각하는 것이 중요했던 것 같습니다.

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

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

이 코드는 어디에 둬야 하는가라는 질문을 스스로 계속 해보는것 그 자체가 이번 챕터의 핵심이었던 것 같습니다.

FSD를 적용하면서 사실 기대했던건 규칙이 있으니까 고민이 줄어들겠지 하는 거였는데, 오히려 그 규칙이 고민을 더 만들수도 있겠구나 하는 느낌을 받았습니다. 물론 사용자 행동이면 features/, UI 블록이면 widgets/, 도메인 로직이면 entities/오 ㅏ 같이 일정부분 편리해진 것도 있지만 모든 프로젝트가 어떤 디자인 패턴에 딱 들어 맞을 수 없듯이 애매함이 나타나는 시점은 항상 나타났습니다.

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

1. comment-manage Feature 세분화에 대한 고민

현재 comment-manage가 댓글의 모든 CRUD를 담당하고 있는데, 이걸 더 세분화해야 할지 고민입니다.

// 현재 - comment-manage (모든 댓글 기능)
export const useCommentManage = () => {
  // 댓글 목록, 추가, 수정, 삭제, 좋아요 모두 관리
  return {
    openPostComments,
    openAddDialog,
    openEditDialog,
    handleAddComment,
    handleUpdateComment,
    handleDeleteComment,
    handleLikeComment,
  }
}

이걸 comment-create, comment-edit, comment-list로 더 나누는 게 좋을까요?

  • 장점: 단일 책임 원칙 준수, 더 명확한 역할 분리
  • 단점: 보일러플레이트 증가, 폴더 구조 복잡화

2. FSD 구조별 테스트 전략

각 계층별로 어떤 테스트 전략을 적용하는 게 효과적일지 궁금합니다. 핑계이긴 하지만 시간 부족으로 테스트 코드까지는 작성하지 못했습니다만,,

  • entities: 순수 함수 단위 테스트 (어댑터 함수들)
  • features: 훅 단위 테스트 (usePostCreate 등)
  • widgets: 컴포넌트 단위 테스트 (PostsTable 등)
  • pages: 통합 테스트

FSD라는 패턴 안에서 어떻게 전략을 짜면 좋을까 고민하다 이런 방향을 생각해봤는데, 다른 좋은 접근법이 있는지 궁금합니다.

과제 피드백

안녕하세요 재환님! 클린코드 마지막 과제 잘 진행해주셨네요 ㅎㅎ 고생하셨습니다!

현재 comment-manage가 댓글의 모든 CRUD를 담당하고 있는데, 이걸 더 세분화해야 할지 고민입니다.

꼭 분리할필요는 없을 것 같아요! 분리해야 한다면 아마 "렌더링 최적화" 때문이지 않을까 싶네요 ㅎㅎ 그래서 분리가 필요할 떄 분리하면 된다고 생각해요! 일단은 현상유지 해도 좋아보입니다

각 계층별로 어떤 테스트 전략을 적용하는 게 효과적일지 궁금합니다. 핑계이긴 하지만 시간 부족으로 테스트 코드까지는 작성하지 못했습니다만. FSD라는 패턴 안에서 어떻게 전략을 짜면 좋을까 고민하다 이런 방향을 생각해봤는데, 다른 좋은 접근법이 있는지 궁금합니다.

말씀해주신 방식대로 진행해도 좋아보여요! 저는 개인적으로 컴포넌트에 대한 테스트는 선호하지 않아서, 아마 features 까지만 테스트할 것 같긴해요 ㅎㅎ

widgets에 대한 테스트는 약간 애매하고, 차라리 pages에 대해 통합테스트를 진행하는게 더 가성비가 있지 않을까!? 라는 생각이 들어요!


고생하셨습니다!!