과제 링크
https://yuyeol.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가 정상적으로 작동하는가?
최종과제
- 폴더구조와 나의 멘탈모데일이 일치하나요?
- 다른 사람이 봐도 이해하기 쉬운 구조인가요?
과제 셀프회고
이번 과제를 통해 이전에 비해 새롭게 알게 된 점이 있다면 적어주세요.
배럴 익스포트를 하는 실용적인 이유를 알게되었습니다.
이때까지는 barrel export를 하는 방식이 존재한다는 것만 알고 사용해야 할 이유를 궁금해하지도, 적용해보지도 않았습니다. 그런데 FSD는 왜 굳이 barrel export를 사용해서 index.ts 파일만 늘려서 구조를 복잡하게 만드는 거지? 라는 의문이 들면서 이유를 찾아보게 되었습니다.
초기 의문:
// 직접 파일 접근이 더 명시적이지 않나?
import { RemovePostButton } from "@features/remove-post/ui/remove-post-button"
import { useRemovePost } from "@features/remove-post/model/use-remove-post"
import { RemovePostButton } from "@features/remove-post" // + index.ts 파일 필요
// useRemovePost는 import 불가 (index.ts에서 export 안함)
실제 적용해보니 알게 된 실용적 이유들:
- 외부에 노출할 것만 export해서 내부 구현을 보호할 수 있다.
- 내부 구조가 바뀌어도 외부 import는 영향 없으니 리팩토링 시 신경이 덜쓰인다.
상대 경로와 절대경로를 혼용하는 이점에 대한 이해도 얻게됨
배럴 익스포트와 함께 언제 상대경로를, 언제 절대경로를 써야 하는지에 대해서도 규칙을 정해서 어기지 않도록 노력했습니다.
처음에는 모든 경로를 모조리 절대경로로 바꾸는게 좋겠다. 라고 생각했지만, FSD import rule 기반으로 AI와 토론을 거친 결과 상대경로와 절대경로를 혼용하는 것이 좋겠다고 판단했습니다.
- 같은 모듈 내부: 상대경로 (
./api,../model) - 다른 레이어 간: 절대경로 (
@shared/ui,@entities/post)
이렇게 했을때 좋은 점:
- 경로만 봐도 "같은 모듈인지, 다른 레이어인지" 바로 구분됨
- 모듈을 통째로 이동해도 내부 상대경로들은 안 깨짐
처음에는 이런 세세한 룰까지 신경써야 하나? 싶었는데, 적용해보면서 경험해보니 배럴과 경로 설정을 통해 코드의 구조로 의도를 표현해보는 연습이 되었던 점이 좋았습니다. 특히 팀 프로젝트에서는 이런 규칙이 잘 지켜진다면 업무 안정성이 높아질 것 같다는 생각이 들었습니다.
막연했거나 고민이 필요했던 부분을 적어주세요.
프로젝트에 FSD 아키텍처를 적용하면서 많이 고민했던 부분 중 하나는 Feature와 Widget의 경계를 정의하는 것이었습니다.
처음에는 직관적으로 CRUD 작업을 기준으로 분리해보자.
- add, edit, remove → 기능 (Feature)
- read → 기능이 아님 (Widget/Entity)
이 기준은 꽤 합리적이라고 느꼈지만, 실제 프로젝트에는 더 복잡한 경우들이 있었습니다.
생각보다 컴포넌트는 복잡한 것 같다...
-
상태 관리가 복잡한 경우:
post-dialog,comment-dialog- 단순 조회가 아닌 복합적인 상태 제어가 필요
- 전역에서 다이얼로그 열기/닫기, 데이터 전달 등
-
같은 조회여도 다른 복잡도:
user-dialogvspost-detail-dialoguser-dialog: 단순 사용자 정보 표시post-detail-dialog: 댓글 CRUD, 좋아요 등 여러 기능 조합
-
UI 제어도 기능일 수 있음:
like-comment- 단순 버튼이지만 optimistic update, 서버 동기화 등 복잡한 로직
3단계 기준에 따른 분석결과로 결론 도출 해보기
- 🟢 HIGH (Feature): 명확한 사용자 행위 + 서버 상태 변경
- 🟡 MEDIUM: 애매한 경우 → 일단 Widget으로 시작, 필요시 승급
- 🔴 LOW (Widget): 순수 UI, 조합, 단순 조회
실제 적용 결과
결과적으로 실제 프로젝트의 모든 위젯을 체크리스트 기준으로 분석한 결과:
| 컴포넌트 | 단일 책임 | 모델 의존성 | 분리 점수 | 상태 |
|---|---|---|---|---|
| add-post | 게시물 생성 | usePostPost | 🟢 | 분리 |
| edit-post | 게시물 수정 | usePutPost | 🟢 | 분리 |
| add-comment | 댓글 생성 | usePostComment | 🟢 | 분리 |
| edit-comment | 댓글 수정 | usePutComment | 🟢 | 분리 |
| remove-post | 게시물 삭제 | useDeletePost | 🟢 | 분리 |
| like-comment | 댓글 좋아요 | usePatchLikes | 🟢 | 분리 |
| remove-comment | 댓글 삭제 | useDeleteComment | 🟢 | 분리 |
| post-dialog | 다이얼로그 상태 | dialog-store | 🟢 | 분리 |
| comment-dialog | 다이얼로그 상태 | dialog-store | 🟢 | 분리 |
| user-dialog | 단순 조회 | GET API만 | 🔴 | 유지 |
| post-filters | 필터/검색 제어 | tags API | 🟡 | 유지 |
| post-detail-dialog | 조합 위젯 | GET + 조합 | 🔴 | 유지 |
| comment-form-dialog | 조합 위젯 | 폼 배치만 | 🔴 | 유지 |
| post-manager-header | 헤더 UI | 없음 | 🔴 | 유지 |
| post-table | 데이터 표시 | 복합 API 호출 | 🔴 | 유지 |
| pagination | 순수 UI | 계산만 | 🔴 | 유지 |
| header | 레이아웃 | 없음 | 🔴 | 유지 |
| footer | 레이아웃 | 없음 | 🔴 | 유지 |
🟢 Feature로 분리한 9개 컴포넌트
- 명백한 사용자 액션 + 서버 상태 변경
🟡 Widget으로 유지한 애매한 1개 컴포넌트
post-filters
- 검색/필터/정렬 제어 기능이 있고 URL 파라미터를 통한 상태 관리 사용
- 하지만 여러 API 호출과 연결되어 있어 완전한 캡슐화가 어려움
- 복합 UI 블록의 성격이 강해 Widget으로 유지
🔴 Widget으로 유지한 8개 명백한 조회 또는 단순 UI용 컴포넌트
- 조합 위젯: post-detail-dialog, comment-form-dialog
- 순수 UI: pagination, header, footer
- 데이터 표시: post-table, user-dialog
Feature와 Widget의 분리 핵심 전략
"점진적 승급 전략이 유효했다."
일단은 모든 컴포넌트는 Widget으로 시작했습니다. 그리고 '이건 명백한 기능이야!' 싶은 것들은 스스로 판단하여 Feature로 승급시키는 전략이 유효했던 것 같습니다.
그리고 명백한 기능이라도 분리하기 복잡한 경우는 로직이 정리되는 동안에는 Widget에서 점진적 개선을 거쳐서 올리면 되지않을까 하는 생각이 직접 경험해보니 실제로 효과적이었던 것 같습니다.
이번에 배운 내용을 통해 앞으로 개발에 어떻게 적용해보고 싶은지 적어주세요.
이번 과제 규모나 지금 회사 규모에서 FSD를 적용한다고 상상해보면, 상황이나 규모가 적절하지 않은 경우도 많겠다는 생각이 들엇습니다. FSD 자체를 실무에 적용할 일이 잘 없을 것 같지만, FSD를 분리해보면서 컴포넌트를 기능 중심으로 본다라는 관점을 가져갈 수 있었던 점은 좋았던 것 같습니다.
평소에 컴포넌트를 그냥 별 생각없이 생성하고 무거워지면 분리하는 일차원적인 작업을 했다고 한다면, 이번에 기능이 있는 컴포넌트와 없는 컴포넌트를 구분해보면서 역할이나 레이어 분리에 대한 감각을 연습 해 볼 수 있었습니다. 기능이 있는 컴포넌트 (Feature):
// 서버 상태 변경 + 사용자 액션
const AddPostButton = () => {
const { mutate } = usePostPost();
return <Button onClick={() => mutate(data)}>게시물 추가</Button>;
};
기능이 없는 컴포넌트 (Widget):
// 단순 조회 + UI 표시
const PostTable = ({ posts }) => {
return <Table data={posts} />;
};
이런 식으로 컴포넌트의 설계를 고려한 코드작성을 한다면, 코드리뷰를 받더라도 명쾌하고 납득이 되는 코드를 보여줄 수 있지 않을까 하는 생각이 듭니다.
결국 완벽한 아키텍처를 도입해야겠다 라는 생각 보다는, 이번에 얻은 관점을 실제 코드 구현에 적응해보도록 사고를 유지해 볼 수 있는 것이 더 의미가 컸습니다.
챕터 셀프회고
클린코드와 아키텍처 챕터 함께 하느라 고생 많으셨습니다! 지난 3주간의 여정을 돌이켜 볼 수 있도록 준비해보았습니다. 아래에 적힌 질문들은 추억(?)을 회상할 수 있도록 도와주려고 만든 질문이며, 꼭 질문에 대한 대답이 아니어도 좋으니 내가 느꼈던 인사이트들을 자유롭게 적어주세요.
이번 챕터를 진행하면서 느낀점을 토대로 얻어간 것이 크게 두가지 있습니다.
소득1: 엔티티 구조를 가장 실용적으로 사용할 것 같습니다.
이번 챕터를 다 해보니 가장 바로 써먹을 수 있겠다 싶었던 건 엔티티 구조였습니다. 엔티티 > 순수함수 > 훅 > 컴포넌트 이런 흐름이 제게는 상당히 합리적이고 현실적인 구조라고 생각이 들었습니다.
저는 그동안 스토어를 만들어도 거대한 리듀서처럼 모든 로직을 스토어 안에 몰아넣었고, 커스텀 훅을 만들어도 "훅 자체가 분리된 모듈이니까 더 분리하는건 좋지 않을거같아" 라고 하며 그 안에서 다시 순수함수를 빼거나 하는 생각은 못했던 것 같습니다.
테오가 평일 Q&A 세션에서 말한 부분 중 "신경 쓰지 않아도 될 코드는 신경 쓰지 않을 곳에 구획을 만들어두고 시선을 두지 않는 것도 좋다"는 말이 인상깊었는데, 엔티티 구조를 사용하면서 비즈니스 로직은 따로 정리해두고, 필요할 때만 꺼내 쓰는 식으로 적용해보면 좋겠다는 생각이 들었습니다.
소득2: 점진적 개선의 중요성을 체감했습니다
회사에서는 제가 혼자 개발하다 보니 리팩토링을 할 때 손바닥 뒤집듯 구조를 갈아엎곤 했습니다. 어차피 저만 보는 코드이기도 해서 불편함도 크게 없었던 것 같습니다.
하지만 팀으로 일한다면 맥락을 공유해야 하는 거고, 팀원들이 따라올 수 있는 속도에 맞춰 점진적으로 개선해야겠다는 생각이 들었고, 구조를 크게 바꾼다면 그만한 합리성과 필요성이 따라와야 되겠구나 느끼게 되었습니다.
테오가 계속 점진적 개선을 강조하는 이유를 제 나름대로 이해한 것 같고, 앞으로는 이 부분을 좀 더 고려해서 리팩토링해보려고 합니다.
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문
-
@features/add-post/ui/add-post-form-dialog.tsx와 같은 컴포넌트는 피쳐로 분리할때 다이얼로그에서 액션 로직과 연결되어있는 부분까지만 분리할지, dialog를 통째로 분리할지 고민하다가 dialog를 통째로 분리했습니다. '모달이 표시된다'라는 현상 자체도 기능으로 보기로 결정했기 때문입니다. 하지만 이 dialog 안에도 add-post라는 기능이 있는데 이런 기능의 중첩 상황같은 경우 세부 분리를 더 진행하는게 좋을까요?
-
@entities/post/api/queries.ts 파일처럼 부담스럽지 않은 코드라인 수준이라면 여러개의 useQuery를 굳이 파일분리를 진행시켜주지 않아도 되겠다 싶어 이번 프로젝트에서는 모아둔 로직들이 많은 것 같습니다. 합리적인 선택이었을까요? 아니면 제가 인지못한 불편함이 존재할까요?
-
항상 질문을 할 때 포괄적인 범위의 질문은 안된다고 전달받았으나, 이번 과제는 괜찮으시다면 파일구조를 잘 구성했는지를 봐주시면 감사하겠습니다. 제가 파일 구조를 잘 짰는지 정말로 궁급합니다.
과제 피드백
수고했습니다. 지난 3주간 클린코드를 비롯한 소프트웨어 공학적으로 결합도 낮추기 응집도 높이기를 위한 이론과 프론트엔드에서의 적용등을 통해서 좋은 코드와 구조에 대한 다각도의 시야가 생겼기를 기대합니다.
회고에서 FSD가 왜 이러한 선택들을 했는지 그리고 그걸 실전에 적용하는 과정에서 어떤식으로 유열이 인사이트를 얻어가는지를 너무 잘 이해할 수 있도록 회고를 잘 써줬다고 생각해요.
"점진적 승급 전략이 유효했다." "...신경 쓰지 않아도 될 코드는 신경 쓰지 않을 곳에 구획을 만들어두고 시선을 두지 않는 것도 좋다"는 말이 인상깊었는데, 엔티티 구조를 사용하면서 비즈니스 로직은 따로 정리해두고, 필요할 때만 꺼내 쓰는 식으로 적용해보면 좋겠다는 생각이 들었습니다."
기능이라고 하는 애매한 경계에 대해서 고민해보고 이후 확장의 개념이 feature slice design하게 만들어지게 되는 이유에 대해서도 잘 생각해보게 된 것 같아서 좋습니다.
Q1) @features/add-post/ui/add-post-form-dialog.tsx와 같은 컴포넌트는 피쳐로 분리할때 다이얼로그에서 액션 로직과 연결되어있는 부분까지만 분리할지, dialog를 통째로 분리할지 고민하다가 dialog를 통째로 분리했습니다. '모달이 표시된다'라는 현상 자체도 기능으로 보기로 결정했기 때문입니다. 하지만 이 dialog 안에도 add-post라는 기능이 있는데 이런 기능의 중첩 상황같은 경우 세부 분리를 더 진행하는게 좋을까요?
=> 분리를 더하고 말고의 기준은 실제로 이들의 결합이 실제 업무나 기능적으로 추상화되어 생각하냐 입니다. post는 액션이 있고 user는 액션이 없지만 대개 다이얼로그를 기준으로 생각합니다. 왜냐하면 그게 제일 확실한 단위라고 생각을 하니까요. 그리고 이 경우에는 CRUD도 하나로 묶어서 생각하게 될거에요. 그러니 세부 분리를 더 진행을 안해도 괜찮을 수 있습니다.
=> 세부분리를 더 해서 폴더 구조를 만든다면? 그건 그거대로의 장점이 있습니다. 여기에는 확실히 삭제, 편집, 생성 기능이 있구나 하는 것을 시각적으로 알게 되죠. 나중에 편집 기능을 수정해야하는 경우라면 해당 기능으로 찾아가거나 분리를 할 수도 있습니다.
=> 요는 더 하는게 좋다 나쁘다의 방향보다는 실제로 그렇게 했을때 도움이 되느냐? 입니다. 도움이 되지 않을 작업을 구태여 복잡하게 일을 벌일 필요는 없겠죠.
Q2) @entities/post/api/queries.ts 파일처럼 부담스럽지 않은 코드라인 수준이라면 여러개의 useQuery를 굳이 파일분리를 진행시켜주지 않아도 되겠다 싶어 이번 프로젝트에서는 모아둔 로직들이 많은 것 같습니다. 합리적인 선택이었을까요? 아니면 제가 인지못한 불편함이 존재할까요?
=> 코드를 폴더 구조나 파일로 드러내주면 모르는 사람이 이 프로젝트를 처음 봤을때 이해도가 높아지게 됩니다. 그런데 3줄 4줄 밖에 안되는 파일들을 많이 만드는게 안 좋을 수도 있죠. 이건 직접 해보고 선택하시면 좋겠네요. 그럴만한 가치가 있는지를 직접 느껴보시면 좋겠습니다. 어떤 선택이든 일리가 있다고 생각해요.
Q3) 항상 질문을 할 때 포괄적인 범위의 질문은 안된다고 전달받았으나, 이번 과제는 괜찮으시다면 파일구조를 잘 구성했는지를 봐주시면 감사하겠습니다. 제가 파일 구조를 잘 짰는지 정말로 궁급합니다.
=> 네 잘 이해하고 보편적인 관점에서 잘 만들어주었다고 생각합니다. 잘했습니다. 폴더명은 케밥케이스로 작성하기는 하나 보통 파일명과 함수명 혹은 컴포넌트명과 비슷한 경우에는 가급적 같은 포맷으로 작성하곤 합니다. 가급적 코드에서 다뤄야할 종류가 많아지는 것은 좋지 않거든요. 폴더구조와 코드면에서는 과제에서 요구하는 대로 잘 해주었다 생각합니다. 수고했습니다.
BP 선정이유: 회고의 작성이 생각의 흐름을 잘 따라가도록 기술이 되어 다른 사람에게도 크게 도움이 될 수 있다고 생각합니다.