areumH 님의 상세페이지[1팀 한아름] Chapter 2-3. 관심사 분리와 폴더구조

과제 체크포인트

배포 링크

https://areumh.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 패턴에 관심이 생겨 공부해보고 싶었는데, 이렇게 과제를 통해 직접 프로젝트에 적용해보며 각 레이어와 도메인 별로 책임을 분리했을 때의 이점을 조금이나마 체감할 수 있었다. 그리고 이번 챕터 과제에 쭉 도메인 상태 업데이트 로직을 순수 함수로 분리하는 방식을 사용하고 있는데 굉장히 깔끔하고 유용하다는 걸 알게 되었다.

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

페이지의 기본 동작을 tanstack query로 구현하기 위해 가장 노력했던 것 같다. 원본 코드처럼 프론트에서 하나의 (전역) 상태로 관리해야 할지, 쿼리 키로 낙관적 업데이트하는 식으로 관리해야 할지 여러 팀원들에게 이리저리 질문하러 다녔던 기억이 난다...

// src/entities/commment/model/store.ts
export const commentModel = {
  /**
   * 댓글 추가
   */
  addComment: (commentData: IComments, newComment: IComment): IComments => {
    return {
      ...commentData,
      comments: [newComment, ...commentData.comments],
    };
  },
}

// src/features/commment/add-comment/model/useAddComment.ts
export const useAddComment = (postId: number, onSuccess?: () => void) => {
  const queryClient = useQueryClient();

  const initialComment: IAddComment = { body: '', postId: postId, userId: 1 };
  const [newComment, setNewComment] = useState<IAddComment>(initialComment);

  const mutation = useMutation({
    mutationFn: (comment: IAddComment) => addCommentApi(comment),

    onSuccess: (createdComment) => {
      const newComment = commentModel.addResponseToComment(createdComment);

      queryClient.setQueryData<IComments>(['comments', postId], (prev) => {
        if (!prev) {
          return {
            comments: [newComment],
            total: '1',
            skip: 0,
            limit: 10,
          };
        }

        return commentModel.addComment(prev, newComment);
      });

      onSuccess?.();
      setNewComment(initialComment);
    },
    onError: (error) => {
      console.error('댓글 추가 오류:', error);
    },
  });

  // 중략 ..
  return { newComment, setBody, addComment };
};

entities 내에 도메인마다 기능에 대해 상태를 업데이트하는 순수 함수를 작성해주었고, 해당 도메인 상태에 관련된 쿼리 훅의 낙관적 업데이트에 사용하도록 했다.

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

dialog 처리 및 게시글 필터 처리 (검색, 태그, 정렬 등) 부분이 개선이 필요하다고 생각한다. 게시글 필터의 경우 지금은 src/widgets/post-filter/ui 내에 로직이 한번에 들어가 있는데 이를 검색/태그/정렬 단위로 잘게 나누는게 더 나은 방식인지 아직도 고민이 끝나지 않는다.. 잘게 쪼개면 기능들이 더 한눈에 보이겠지만 반대로 너무 컴포넌트가 세분화되어 관리가 번거로워질 수도 있다는 생각이 든다.

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

이번 과제를 하면서 팀원들과 각 계층의 역할과 구조를 두고 의견이 많이 갈리는 경험을 했다. 그래서 만약 내가 실제 회사에서 fsd 구조를 도입하고 싶다는 생각이 들어도 , 합의와 이해가 없으면 반영이 쉽지 않겠다는 생각이 들었다.. 대신 혼자 진행하는 개인 프로젝트에서 자유롭게 fsd 구조를 적용해보며 구조 설계 및 유지보수 효율성을 더 깊이 경험해보고 싶다.

챕터 셀프회고

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

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

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

더티 코드를 보면 어디부터 손을 대야 할지 막막하고, 읽을수록 머릿속이 복잡해지는 것 같다.. 클린 코드는 변수와 함수 이름만 봐도 역할이 떠오르고, 어느 한 부분을 수정할 때 다른 부분에 영향을 최소화하도록 하는 코드라고 생각한다.

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

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

이번 과제에서 데이터의 낙관적 업데이트를 위해 각 도메인의 상태를 변경하는 순수 함수를 사용했고, Dialog 모달 처리에 zustand의 전역 상태 관리를 사용했다. 처음 과제를 딱 봤을 때 dialog 처리를 어떻게 해야할 지가 제일 막막했는데, 전역 상태를 도입하며 라이브러리의 편리함을 크게 느꼈다... 전역 상태에 컴포넌트를 직접 주입하는 방식에는 약간 아쉬움이 남지만, 최선을 다해 구현했다고 생각한다.

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

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

각 entities에 관련 api 함수를 모두 정의해주었고, 단순 조회의 쿼리 훅은 entities에, 사용자의 행동에 직결되는 기능의 쿼리 훅은 features 내에 정의했다. (useQuery - entities / useMutation - features 와 같은 구조) 이렇게 구조를 나누니 코드를 어디에 두어야 할지 고민하는 시간이 줄고, 각 레이어의 책임이 보다 명확해진 것 같다. 이전에 프로젝트를 진행할 때 하나의 도메인에 대한 쿼리 훅을 useQuery와 ueMutation의 역할에 관계없이 하나의 폴더에 모아두어특정 기능과 관련된 파일을 찾을 때 이리저리 뒤져야 하는 불편함이 있었는데, 이번에 fsd 폴더 구조를 기반으로 프로젝트를 진행하며 기능 별로 분리하는 것의 편리함을 크게 느꼈다.

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

파일 위치 및 구조를 제일 고민했던 부분은 dialog (대화상자 모달) 관련 로직 및 ui인데요. 코치님이라면 해당 부분을 어떻게, 어느 위치에 두고 리팩토링을 하실 지 궁금합니다.

저는 dialog 처리를 zustand로 진행했고, dialog의 정보를 담는 틀 컴포넌트(src/widgets/dialog/DialogRoot.tsx)는 여러 entities 및 features가 안에 포함될 수 있으니 widgets에, 관련 처리 훅(src/shared/hook/useDialogStore.ts)은 여러 features와 widgets에서 사용될 수 있으니 shared에 두었습니다. 그런데 이 구조가 fsd 패턴에 맞는 구조인지 의문이 들더라구요. 틀 컴포넌트도 shared에 들어가야 하나? 모달 처리 훅도 하나의 기능으로 보고 features에 넣어야 하나? 기능이라기보단 사용자의 액션(게시글 추가, 댓글 수정 등)에 따라오는 부가 기능일 뿐인가? 훅을 DialogRoot 컴포넌트와 같은 폴더에 넣어야 하나? 하는 고민을 수도 없이 했던 것 같아요.

만약 저처럼 zustand로 구현한다면 어떤 구조가 더 나을 지, 아님 아예 zustand로 처리하지 않는다면 어떤 방식으로 구현하실 지 알고 싶습니다 !!

과제 피드백

수고했습니다. 지난 3주간 클린코드를 비롯한 소프트웨어 공학적으로 결합도 낮추기 응집도 높이기를 위한 이론과 프론트엔드에서의 적용등을 통해서 좋은 코드와 구조에 대한 다각도의 시야가 생겼기를 기대합니다.

"게시글 필터의 경우 지금은 src/widgets/post-filter/ui 내에 로직이 한번에 들어가 있는데 이를 검색/태그/정렬 단위로 잘게 나누는게 더 나은 방식인지 아직도 고민이 끝나지 않는다.. 잘게 쪼개면 기능들이 더 한눈에 보이겠지만 반대로 너무 컴포넌트가 세분화되어 관리가 번거로워질 수도 있다는 생각이 든다."

Q) 파일 위치 및 구조를 제일 고민했던 부분은 dialog (대화상자 모달) 관련 로직 및 ui인데요. 코치님이라면 해당 부분을 어떻게, 어느 위치에 두고 리팩토링을 하실 지 궁금합니다.

=> 다이얼로그 show/hide를 하는 코드를 widgets에다가 하나 만들고 나머지 Dialog등을 표기하는 방식으로 했군요. 좋습니다. 나쁘지 않은 방식이라고 생각해요.

=> 저는 PostDialog를 만들어서 하나로 만들어서 컴포넌트에 모아서 두거나 하위 폴더를 만들어서 AppDialogs를 만들어 관리를 했을 것 같아요. 하나의 위젯단위라기 보다는 페이지 일부에 속하는 섹션인것 같아서 Dialogs 모음을 만들거라면 pages/ui나 app/ui 에 둘것 같아요.

Q) 만약 저처럼 zustand로 구현한다면 어떤 구조가 더 나을 지, 아님 아예 zustand로 처리하지 않는다면 어떤 방식으로 구현하실 지 알고 싶습니다 !!

=> zustand를 쓰지 않을거라면 jotai를 쓸거에요. reocil와 redux가 표준에서 내려온 지금 전역상태관리 라이브러리로써는 저 2개가 제일 많이 쓰인다고 생각합니다. 확실히 zustnad의 채택률이 제일 좋으니 그걸 쓰시면 좋겠네요

수고하셨습니다.