일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- TypeScript
- wanted-preonboarding-course
- 회고
- error
- refactoring
- CRUD w ReactQuery
- 짝선배 짝후배 매칭 웹 개발 회고
- teave
- 멀티프로세스
- Spa
- CS
- 멀티스레드
- Today
- Total
깊고 넓은 삽질
[리팩터링 회고] 굿 바이 Teave | 1. 폴더 구조 고민은 이제 그만.. 본문
밀린 리팩터링 조지기
5월쯤 부터 시작한 티 플랫폼 사이트 개설 프로젝트 teave가 어느덧 5개월이 지나고 있다. 그간 나름의 리팩터링을 해왔었지만 최근 한 달동안은 기능 구현에만 초점을 맞췄던 것 같다. 이제 곧 teave 팀에서 나가야 하는데 후임 프론트 엔지니어에게 이 상태로 넘겨 줄수는 없다.
현재 teave의 프론트엔드 코드는 구조가 뒤죽박죽 되어있다. 처음부터 끝까지 나 혼자 짠 코드들이지만 중간중간에 새로운 패턴을 학습해 적용해 여러 패턴이 섞여있다. 이를 하나의 직관적인 패턴으로 통일하는 것이 1차적인 목표다.
2차 목표는 컴포넌트 분리다. 플랫폼 사이트 특성상 비슷한 컴포넌트들이 많다. 그런데 각자 역할과 기능이 조금씩 달라서 새로 만들다 보니 현재 여러 컴포넌트들에서 중복되는 부분이 많다. 특히 상품을 옆으로 넘겨 볼 수 있는 슬라이더를 직접 구현한 부분에서 약 세 가지 버전이 있는데 이를 각각 따로 구현해 재사용이 전혀 되고있지 않다.
현재 폴더 구조 펼치기 ⬇
.
├── components
│ ├── FilterModal.tsx
│ ├── PromotionBanner.tsx
│ ├── SortModal.tsx
│ ├── TeaItem.tsx
│ ├── TeaRecommend.tsx
│ ├── ThemeRecommend.tsx
│ ├── common
│ │ ├── Button.tsx
│ │ ├── InputText.tsx
│ │ ├── Margin.tsx
│ │ ├── Modal.tsx
│ │ ├── Slider.tsx
│ │ └── TitleHeader.tsx
│ └── layout
│ ├── CenteredContainer.tsx
│ ├── HeaderScroll.tsx
│ ├── HeaderTemplate.tsx
│ ├── HomeHeader.tsx
│ ├── HomeLayout.tsx
│ └── TabBar.tsx
├── hooks
│ ├── useElementCurWidth.ts
│ ├── useFilterModal.ts
│ ├── useInput.ts
│ ├── useQustionProvider.ts
│ ├── useSlider.ts
│ └── useSliderButton.ts
├── pages
│ ├── _app.tsx
│ ├── _document.js
│ ├── cart
│ │ ├── components
│ │ │ ├── Bill.tsx
│ │ │ ├── CartFooter.tsx
│ │ │ ├── CartProduct.tsx
│ │ │ └── ToggleSelector.tsx
│ │ └── index.tsx
│ ├── home
│ │ ├── index.tsx
│ │ ├── magazine.tsx
│ │ ├── shop.tsx
│ │ └── tea-test
│ │ ├── components
│ │ │ ├── QuestionCard.tsx
│ │ │ └── QuestionProvider.tsx
│ │ └── index.tsx
│ ├── magazine
│ ├── mypage
│ │ ├── find.tsx
│ │ ├── index.tsx
│ │ ├── login.tsx
│ │ └── signup.tsx
│ ├── order
│ │ ├── components
│ │ │ ├── DeliveryInfo.tsx
│ │ │ └── OrderProduct.tsx
│ │ └── index.tsx
│ ├── product
│ │ ├── components
│ │ │ ├── Question.tsx
│ │ │ └── Review.tsx
│ │ └── index.tsx
│ ├── search
│ │ ├── components
│ │ │ └── SearchBar.tsx
│ │ ├── hooks
│ │ │ └── useSearch.ts
│ │ └── index.tsx
│ ├── shop
│ ├── tea-test
│ └── wish
│ └── index.tsx
├── services
│ ├── api
│ │ ├── authApi.ts
│ │ ├── teaApi.ts
│ │ └── validateApi.ts
│ ├── hooks
│ │ ├── useAuthQuery.ts
│ │ ├── useMainProdutsQuery.ts
│ │ ├── useTeaActionQuery.ts
│ │ └── useThemeProductsQuery.ts
│ ├── model
│ │ ├── articleSchema.ts
│ │ ├── authSchema.ts
│ │ ├── cartSchema.ts
│ │ ├── queryKey.ts
│ │ ├── teaSchema.ts
│ │ ├── userSchema.ts
│ │ └── validateSchema.ts
│ └── static
│ ├── dummy.json
│ ├── teaFilter.json
│ └── teaTestQuestions.json
├── styles
│ ├── globalStyles.tsx
│ └── palette.tsx
├── tree.txt
├── types
│ ├── common.ts
│ ├── filter.ts
│ ├── mainPageProps.ts
│ └── search.ts
└── util
└── storage.ts
30 directories, 76 files
우리 프로젝트에 알맞은 디자인패턴 / 폴더구조
가장 고민되고 중점적으로 생각했던 것은 직관적인 구조 구축이었다. 제일 첫 고민은 page 폴더였다. 어떤 기준으로 폴더를 나눌지, 모든 파일들을 폴더 단위로 관리할 지 고민이 됐다.
현재 우리 사이트의 구조는 다음과 같았다.

페이지들의 관계가 복잡해 기능이나 네비게이터 기준으로 나누기가 애매했다. 사실 프로젝트를 진행하는 5개월 내내 구조에 대한 고민이 끊이질 않았다. 어떻게 하든 직관적으로 페이지 구조를 짜기가 어려웠다. 다른 이커머스 사이트들의 페이지 라우팅을 살펴보기 위해 url로 구조에 대한 추측을 해봤지만 직관적인 구조는 찾기 어려웠다.
복잡도 줄이기 | 일관성에서 깊이 축소로, 관심사 분리
구조에 대한 정답을 찾기가 너무 길어져서 일단 복잡도를 줄이는 것에 주안점을 뒀다.
일관성에서 깊이 축소로 ::
처음에는 직관적인 구조를 지키려고 일관성을 중요하게 생각했었다. 그래서 모든 페이지들을 폴더단위로 구축하고 컴포넌트와 훅들을 함께 넣는 구조를 생각했다. 그런데 Next.js에서는 page 폴더에 있는 파일들이 모두 라우팅 처리되니까 컴포넌트나 훅을 넣는 것을 지양해야 한다. 이러다보니 깊이가 1인 폴더들은 index파일만 존재해 무의미한 깊이 증가를 초래했다. 그래서 일관성을 포기하고 깊이가 필요한 페이지만 폴더를 만들고 나머지는 모두 루트에 파일 단위로 두었다. 컴포넌트들과 훅들은 모두 다시 page 밖으로 빼냈다.

여담으로, 올해 5월에 Next.js에서 발표된 앞으로 나올 Next.js의 새로운 버전은 app이라는 폴더가 page 폴더를 대신한다고 한다. 여기에는 컴포넌트나 훅들을 같이 넣을 수 있다고 한다. (Next.js Layout RFC 링크) 이 때는 RFC가 뭔지 모르고 page파일과 그 파일이 사용하는 컴포넌트, 훅들을 함께 넣는 구조를 구상했다. 그러나 RFC가 청사진인걸 뒤늦게 깨닫고 다시 페이지에 사용된 컴포넌트들과 훅을 따로 분리했다..
관심사 분리 ::
관심사의 분리는 처음엔 뷰와 비지니스 로직으로 나누려고 시도했다. 그런데 비지니스 로직이라고 할만한 게 없었다. 비지니스 로직은 백엔드에서 처리하고 프론트는 API로 추상화된 비지니스 로직들을 사용하기만 하면 되는 것 아닌가? 그래서 컴포넌트의 복잡성을 줄이는 것으로 방향을 잡았다. 처음 시도한 것은 API를 사용하는 코드를 분리하는 것이다. 이 부분은 react query 프레임워크를 사용하면서 잘 분리가 되었다. services라는 폴더 안에 서버와 관련된 로직들을 모두 넣었고 services/hooks에는 react query 프레임워크를 사용한 코드들, api에는 fetch하는 함수들을 넣었다. model에는 서버에서 쓰이는 데이터들의 스키마를 타입으로 지정해뒀다.

api를 분리할 때 react query가 특히 편했는데, over fetching된 데이터들을 select로 정제할 수 있었고, 데이터가 변경되는 요청을 보내면 지정한 쿼리키를 기준으로 캐시를 무효화해줬다.
이렇게 해서 서버의 데이터는 모두 react query를 이용해 fetch한 데이터들을 바로 사용했고, 컴포넌트 내부에서 사용하는 상태 데이터는 오직 클라이언트에서 필요한 상태만 취급하게 되었다.
이 외에도 컴포넌트 내부에서 복잡성을 줄이기 위해 상태관리 로직을 모두 hook으로 빼줬다.
남은 일 | 컴포넌트 분리, 테스트 코드 작성
이번 프로젝트에서는 비슷한 컴포넌트인데 기능과 디자인이 약간씩 다른 경우가 많았다. 이럴 때마다 어떻게 분리할 지 고민하다가 결국 각각 따로 만든 경우가 많았다. 이제 이런 컴포넌트들을 다시 분리해야한다..
사실 리팩토링 하기 전에 했어야 했는데 지금까지 미뤄둔 것이 있다.. 다름 아닌 테스트 코드 작성이다.. E2E나 통합테스트까진 아니더라도 유닛테스트정도는 만들어야 했는데 계속 미뤄왔다.. 지금까지 테스트는 직접 프론트서버를 띄워서 눈으로 확인하는 방법이었고, 모킹데이터는 프론트서버 내부에 있는 json파일을 import해서 사용했다. json 파일을 import하는 부분들을 모두 없애고 환경변수를 이용해 프론트와 백, 프로덕트 환경으로 분기해 테스트시 모킹데이터를 받아오도록 바꿔야한다.