Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: FDA(Feature-Driven Architecture) 기반 폴더 구조 변경 및 react-query 구조 개선 #53

Open
7 tasks done
ojj1123 opened this issue Jun 12, 2023 · 0 comments
Assignees
Labels
do not close do not close good first issue good first issue refactor Refectoring RFC Propose for refactoring or something else

Comments

@ojj1123
Copy link
Collaborator

ojj1123 commented Jun 12, 2023

정보

  • 담당자 : @ojj1123
  • 개요 : react-query 구조 개선 및 비즈니스 로직 관심사 모으기

현재 상황

각 비즈니스 로직에 대한 관심사가 흩어져 있다

한 API 추가 시 건드려야 하는 파일이 5개정도다. (infra/api, hook/api, 쿼리키 파일, 타입 파일, mock handler 파일) 이는 응집도를 떨어뜨려 커지는 서비스 규모에 따라 개발 생산성 저하가 있을거라 생각한다.
아래 PR에서 그 문제점을 볼 수 있다.

  1. Feat: 태그 검색 페이지 썸네일 구현 #28
    → 태그 검색 결과 API에 필드를 수정하기 위해 mock handler, infra/api, hook/api, type 파일 4개의 파일을 모두 수정해야 했다.
  2. ♻️ 즐겨찾기 태그 api , 추가/삭제 로직 반영 #12
    → 태그 즐겨찾기 관련 API를 수정하기 위해 mock handler, infra/api, hook/api, querykey 파일 4개의 파일을 수정해야 했다.
    → 태그 즐겨찾기 관련 컴포넌트 (CategoryContent, TagBookmarkButton)도 추가로 수정해야했음

폴더구조가 복잡하다

비즈니스 로직이 여러 파일에 흩어져 있어 API 추가 시 여러 파일을 건드려야 해서 API수가 많아진다면 충분히 실수할 여지가 있다. 또한 API 수정/삭제 시 여러 파일을 바라봐야 해서 유지보수성과 생산성이 떨어진다. 따라서 아래와 같은 문제가 있다.

  1. 폴더구조가 복잡하고 depth가 많이 들어간다.

    • 우리가 소통할 때 applicationinfra 폴더를 많이 언급했는가? 개인적인 경험상 그러지 않았다
    • API 하나 추가/수정/삭제하더라도 여러 파일을 바라봐야한다 -> 유지보수성과 생산성 저하
  2. 같은 페이지 내 로직들이 흩어져 있다.

  • 프로젝트 규모가 커질 수록 어떤 기능과 연관된 코드를 탐색하는 과정이 느려지고 어려워진다. 정확한 의존관계에 놓여있는 코드를 탐색하기 위해 복잡한 구조의 폴더와 코드를 순차적으로 탐색해나가야 하기 때문이다.

리팩터링1. 폴더구조 개선

FDA(Feature-Driven Architecture)로 변경하고자 한다. FDA를 통해 코드의 역할과 책임을 나누고, 같은 관심사의 코드를 응집시켜 필요한 로직과 그 의존관계를 빠르게 탐색할 수 있도록 개선하고자 한다.

src
- pages -> 페이지 컴포넌트

- features -> ⭐️ 페이지 기준으로 feature를 나눔
  - page1
    - components
    - hooks
  - page2

- api -> API 요청/응답 관련 로직, react-query 커스텀 hook을 모아둔 폴더
  - domain1
    - useGetMemes.tsx
    - usePostMeme.tsx
  - domain2

- common -> 공통 hook, component, util
  - hooks
  - components
  - utils
  - types

리팩터링2. react-query 구조 개선

우성님 조언

  • 같은 관심사의 API가 너무 멀리 흩어져 있다. → 유지보수와 테스트하기 힘든 환경
  • 오히려 다른 관심사 API가 같은 파일에 존재해 있다.

react query maintainer도 쿼리키와 커스텀 훅 그리고 react query와 관련된 모든 것들을 같은 파일에 두는 것을(colocation) 권장하고 있다.
https://tkdodo.eu/blog/effective-react-query-keys#effective-react-query-keys
effective-react-query-keys

같은 쿼리키를 가지는 관심사(같은 API)는 최대한 같은 파일에 위치시킨다.

AS-IS
  • queryFn
// src/infra/api/tags/index.ts

export class TagApi {
  constructor(private api: AxiosInstance) {}

 ...

  getFavoriteTags = () => {
    return this.api.get<GetFavoriteTagsResponse>("/tags/favs").then((response) => response.data);
  };
}
  • Response Type
// src/infra/api/tags/types.ts
...
export type GetFavoriteTagsResponse = Pick<Category, "tags">;
...
  • React query custom hook
// src/application/hooks/api/tags/index.ts
...
export const useGetMemeTagsById = (id: string) => {
  const { data, ...rest } = useSuspendedQuery({
    queryKey: QUERY_KEYS.getMemeTagsById(id),
    queryFn: () => api.tags.getMemeTagsById(id),
    staleTime: Infinity,
  });
  return { ...data, ...rest };
};
...
  • queryKey
// src/application/hooks/api/tags/queryKey.ts

export const QUERY_KEYS = {
 ...
  getFavoriteTags: ["getFavoriteTags"],
  getMemeTagsById: (id: string) => ["getMemeTagsById", id],
 ...
} as const;
  • Mock handler
// mocks/handlers/tags/index.ts
...
export const getFavoriteTags = rest.get(
  `${process.env.NEXT_PUBLIC_API_URL}/tags/favs`,
  async (req, res, ctx) => {
    return res(
      ctx.delay(300),
      ctx.status(200),
      ctx.json({
        tags: MOCK_DATA.favoriteTags,
      }),
    );
  },
);
...
TO-BE
  • type, react query, queryFn, queryKey를 하나의 파일에서 관리할 수 있다.
// 응답 타입도 쿼리와 같은 파일에 묶어둔다.
type Response {
 property1: Type1;
 property2: Type2;
 ...
}

const useGetTags = () => {
 return useQuery({
  queryKey: useGetTags.queryKey(...);
  queryFn: useGetTags.queryFn
  ...
 })
}

// 함수도 객체이므로 같은 쿼리에 대한 관심사를
// 다음과 같은 컨벤션으로 묶어둘 수 있다.
useGetTags.queryKey = (params) => [params, ..., ...];

useGetTags.queryFn = () => {
//  return axios.get<Response>("/endpoint").then((response) => response.data);
//  return request<Response>({url: '/endpoint', method: 'GET' });
}

// 해당 쿼리키에 대한 추가적인 작업이 생길 경우
// 같은 리액트 쿼리 파일 내에 메서드를 추가해
// 같은 관심사끼리 묶어두기 용이하다
useGetTags.removeQueries = () => {
 queryClient.removeQueries(...)
}
  • (추가) axios 관련 모듈화
import { AxiosRequestConfig, AxiosResponse } from 'axios';

import axios from '@/shared/configs/axios';

export const request = <T,>(config: AxiosRequestConfig) =>
  axios(config).then((response: AxiosResponse<T>) => response.data);

우리 서비스에 좋아지는 것

  • 기능이 추가되고 서비스가 커짐에 따라 대처하기 용이해질 것이라 생각해요.
  • 비즈니스 로직 관심사가 한 곳에 모여서 테스트나 성능 파악, 수정 등이 이전보다 수월해질 것이라고 생각해요.

팀원들에게 도움되는 것

  • 서비스를 지속가능하게 유지하기 위해 고민해보고 서비스 유지보수 경험을 가져갈 수 있다고 생각해요
  • 바뀐 아키텍처, 컨벤션이 추가되는 기능(API, 기획 등)에 대응하는 것이 이전 컨벤션보다 편할 것이라 생각해요.

체크리스트

#54

  • 공통 컴포넌트 이동(components/common -> common/components)
  • 공통 Hook 이동(application/hooks/common -> common/hooks)
  • 공통 유틸 이동(application/util -> common/utils)
  • 서드파티 라이브러리 이동(infra/sdk -> common/libs)

#58

  • 페이지별 컴포넌트 이동(components/{domain,page..} -> feature/{page}/components)
  • 페이지별 hook 이동(application/hooks/domain -> feature/{page}/hooks)

#61

  • React query, 비동기 API 리팩터링
@ojj1123 ojj1123 added refactor Refectoring RFC Propose for refactoring or something else labels Jun 12, 2023
@ojj1123 ojj1123 self-assigned this Jun 12, 2023
@ojj1123 ojj1123 changed the title RFC: react-query 구조 개선 및 비즈니스 로직 관심사 모으기 RFC: 폴더 구조 변경 및 react-query 구조 개선 Jul 22, 2023
@ojj1123 ojj1123 added the do not close do not close label Sep 11, 2023
@ojj1123 ojj1123 added the good first issue good first issue label Feb 5, 2024
@ojj1123 ojj1123 pinned this issue Mar 16, 2024
@ojj1123 ojj1123 changed the title RFC: 폴더 구조 변경 및 react-query 구조 개선 RFC: FDA(Feature-Driven Architecture) 기반 폴더 구조 변경 및 react-query 구조 개선 May 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
do not close do not close good first issue good first issue refactor Refectoring RFC Propose for refactoring or something else
Projects
None yet
Development

No branches or pull requests

1 participant