반응형

지난 2022년 12월 ~ 2023년 2월 1일간 했던 일을 간단하게 메모해두려고 한다

아는사람?의 추천을 받고 작년 10월에 새로운 회사에 입사했다.
입사하고 기존 레거시 프로젝트의 react 개발자로 일하게 되었는데,
코드를 받아보고 정말 충격을 받았었다.

가만히 놔두면 렌더링이 무한 발생하는 실시간 차트 페이지..; 이 페이지는 결국 버리고 새로 만들었다

정말 개판으로 짜였다고 느껴진 스파게티 코드와
!important와 inline으로 갈겨놓은 css들을 보고 경악을 했어서 처음엔 나갈려다가
채용 한파에 1년만 버티자 하고 울며 겨자먹기로 ㅠㅠ 일하게 되었는데

하다보니 애증?이 생겨서 열심히 하고 있는 중이다.

1. ground rule 다시 만들기

(아래 링크는 입사 이후 회의한 내용)
https://www.notion.so/10-13-e2521a2a8b5944ddb4e873da3f00cd26

 

10/13 회의 내용

1. import alias - 반영 완료

www.notion.so


프론트 개발은 중간에 한분이 나가시고 사실상 사수분 한명이 다 하고 있던 상태였는데 (어떻게 하셨는지 지금도 의문..)
협업을 위해 간단한 ground rule을 다시 세우고 작업하기로 했다.

프로젝트가 몇년간 여러 사람을 거쳐가면서 누구는 presentational-container pattern을 사용하고 누구는 그걸 따라하겠다고 굳이 mobx에서 전역 데이터를 꺼내서 최상단에서 최하단 7층까지 내려보내고 있고;; 통일성이 필요했다.

2. 디자인 시스템 - 공통 컴포넌트 분리하기

디자인 시스템 예시(출처 : https://coyleandrew.medium.com/a-quick-guide-to-creating-a-design-system-7888e267171f)

기존에는 같은 컴포넌트를 ctrl c + ctrl v 해 사용하거나, material UI를 그대로 박아넣어서
수정 사항이 생겼으면 모든 컴포넌트를 바꾸는 노가다를 해야했다.
(공통 컴포넌트가 있긴 했지만 사람이 계속 바뀌면서 쓰이지 않았다.. 이유는 3번 storybook 문단에서 후술함)

실제로 입사 초기에 dialog에 어떤 기능을 추가하기 위해서 46개의 dialog가 포함된 파일을 수정했었다. ....

그래서 디자인팀에 요청해서 재사용성을 높이기 위해서 같은 컴포넌트를 묶어주고
컴포넌트화 시키는 작업을 하자고 제안했는데, 마침 디자인팀도 일관된 사용자 경험을 위해서 비슷한 고민을 하고 있던 참이라서 공통 컴포넌트 작업을 시작하게 되었다.

디자인 템플릿은 구글의 Material UI를 참고했고, 코드 형식은 Headless UIcompound Pattern를 참고했다.

공통 컴포넌트인 accordion 예시

1. 우선 많이 쓰이는 컴포넌트 (Dropdown, button 등)등을 분리했고,
2. (button 같이 여러 style 바리에이션이 있는 경우) 디자인에 따라서 primary, secondary 등 여러 css 템플릿을 만들었다. props로 전달해서 css 형식을 정하게 된다.
3. 그리고 공통 컴포넌트 내부에서 상태를 Depth에 상관없이 공유하기 위해서 Compound pattern과 Context API를 사용해
작성했다.

 

기능과 스타일의 분리

 

구현시 재사용성을 위해서 기능(logic) 부분과 style이 분리되게 설계했다.

한 기능(ex- dropdown)에 여러 style을 갈아끼울 수 있으며,

기능과 style이 서로 독립적이니 객체지향의 OCP처럼 점점 필요한 기능을 확장해나갈수 있게 설계했다.

 

위 예시 사진인 accordion도 dropdown의 logic 부분 코드을 재활용하고 style을 따로 입힌 예시이다.

 

참고) compound pattern + context api - https://leon-dunamu.github.io/2021/07/21/react-compound-component/

2-1) 라이브러리 모듈화

이때 라이브러리를 사용했다면(ex - table 이나 Map API) 반드시 공통 컴포넌트로 한번 감싸고
외부에 인터페이스를 공유하는 식으로 작성했다.

왜냐하면 이 공통 컴포넌트를 사용하는 입장에서는 내부 구현을 알 필요가 없다.

지도로 비유하자면

컴포넌트를 사용하는 입장에서 Kakao Map인지 Naver Map인지는 관심이 없고 '지도 자체'만 필요하기 떄문이다.

또한, 모듈화가 이뤄져야 만약 라이브러리에 문제가 생겼거나 교체가 필요하다면 인터페이스는 놔두고 공통 컴포넌트 내부만 바꾸는 식으로 빠르게 교체할 수 있다.

+ 모듈화를 통해 '경계'를 만들고 라이브러리 전용 테스트를 작성해서 만약 라이브러리 버전업시 문제가 발생하면 빠르게 캐치할수 있도록 jest로 test code를 작성했다.

추가적으로 기본적인 dropdown 컴포넌트 등을 만들때엔 기존에는 mui4를 사용했는데 점차 deprecated 시킬 예정이라서 외부 라이브러리를 사용하지 않고 직접 작성했다.
(지금 생각해보니 아이콘은 mui자너..?)

3. Storybook 도입

https://storybook.js.org/

사람이 자주 바뀌는데 문서화가 안되있으니
전임자들 코드를 분석해보니 비슷한 코드를 2~3번씩 작성하거나
1회용이라 생각해서 그냥 작성해 때려박는 문제점이 있길래 인수인계 + 공통 컴포넌트 테스트 용으로 storybook을 도입했다.

1. 공통 컴포넌트를 작업한 뒤

2. storybook을 작성하고

3. 그 storybook을 jest에서 import해서 테스트를 돌린다 (mocking - MSW)
이러면 테스트 대상이 어떻게 렌더링되나 실제로 브라우저에서 볼 수 있는 장점이 있다.

테스트 방식은 전에 글을 한번 썼었다.
https://lodado.tistory.com/73

4. visual regression test 용으로 chromatic 에 연동해 확인한다.

(그런데 모든 snapshot을 체크해보기에는 사람이 부족하기도 하고 chromatic은 단순 참고 용도로 사용하고 있다..
확인하는 부분은 나중 QA팀에 요청할 생각이다)

4. 데이터 정규화 - normalizr 도입

(normalizr 소개는 quick start 참고 - https://github.com/paularmstrong/normalizr/blob/master/docs/quickstart.md)

{
  "id": "123",
  "author": {
    "id": "1",
    "name": "Paul"
  },
  "title": "My awesome blog post",
  "comments": [
    {
      "id": "324",
      "commenter": {
        "id": "2",
        "name": "Nicole"
      }
    }
  ]
}


normalizr를 간단하게 설명하자면 위와 같이 깊은 depth를 가진 JSON 파일에서 data.comments.commenter.id 만 3으로 수정한다고 생각해보자. 매우매우매우 끔찍하다.
그래서 정규화 과정을 통해 데이터를 id를 통해 참조하는 쓰기 좋은 포맷으로 바꿔준다.

{
  result: "123",
  entities: {
    "articles": {
      "123": {
        id: "123",
        author: "1",
        title: "My awesome blog post",
        comments: [ "324" ]
      }
    },
    "users": {
      "1": { "id": "1", "name": "Paul" },
      "2": { "id": "2", "name": "Nicole" }
    },
    "comments": {
      "324": { id: "324", "commenter": "2" }
    }
  }
}


위와 같이 id를 통해 참조 가능한 1 depth로 줄여주는 좋은 라이브러리다.

내가 맡은 파트는 비유하자면
"레이아웃에 drag and drop으로 자유롭게 배치 가능한 실시간 주식 차트"인데, API 구조가 상당히 복잡하다.

주식 차트들을 보기 위해서 다음과 같은 과정을 거친다.

1. 유저의 LayoutList 목록을 API 콜 A로 가져온다.
2. 유저가 LayoutList 중 하나의 Layout을 선택한다.
3. 해당 선택된 Layout에는 Chart들의 정보를 담는 ChartList가 있다. ChartList 에 있는 chart 들의 key들을 API 콜 B로 보내서 chart를 그릴 데이터를 API로 받아온다.

기존 코드는 해당 API를 사용하기 위해서 매우 복잡하고 파악이 힘든 전처리 과정을 거치고 있었는데

normalizr를 사용해 id 값만 있다면 어느 데이터나 (전역 state를 통해) 접속하고 , 수정 가능하게 리펙토링하니
가독성이 높아지고 유지보수성도 확실히 향상되었다.

나도 써보기 전까진 긴가민가 했는데 이젠 안쓰고는 못살꺼 같다.
API 구조가 복잡하면 꼭 써보자.

 

2023-02-18 추가 //

비슷한 내용 찾아서 추가..

 

https://www.youtube.com/watch?v=HYgKBvLr49c 

 

 

5. Repository 패턴 도입

안드로이드의 레포지토리 패턴 예제


프론트에서 필요한 데이터의 접근과 비즈니스 로직을 분리하기 위해서 API 접근에서 레포지토리 패턴을 추가했다.
추가적으로 필요한 데이터 전처리, 데이터 정규화(normalizr), 데이터 캐싱 처리를 레포지토리에서 관리하도록 layer를 분리했다.

기존에는

component <-> (state library(mobx, redux 등등..)) <->  API Call

위와 같은 구조로 되어 있었는데 필요한 데이터를 뽑아내는 전처리 로직과 비즈니스 로직이 뭉쳐있어서
API 수정이나 프론트 코드 수정에 유연하지 못했다.

repository를 추가한다면

component <-> (state library(mobx, redux 등등..)) <-> repository  <->  API Call

위와 같은 레이어를 거치는셈이다.
repository가 데이터 관리를 수행하고 사용하는 입장에서는 그냥 데이터 요청만 해서 써도 된다.

여담으로 repository가 데이터 전처리까지 해도 좋은가(=프론트에서 전처리해도 좋은가) 조사해본 결과 데이터 전처리는 가능하면 백엔드에서 하는게 좋은 편인거 같다. 프론트엔드는 싱글 쓰레드로 상태관리와 렌더링하는거만 해도 바쁘다.
그래서 일부 기업은 프론트가 백엔드인 API gateway server까지 전담하는 경우도 많다고 한다.

우리팀도 점진적으로 리펙토링 해가며 전처리 로직을 백엔드로 넘길 예정이다. graphql을 도입하여 프론트에서는 질의문만 백엔드에 날릴까 검토중인데 큰 작업이라 진짜 할지는 모르겠다.

추가적으로 백엔드 API가 기존에는 페이지 별로 있었는데 컴포넌트 단위로 통합시킬 예정이다. 그래서 1차로 동일한 인터페이스를 가진 레포지토리로 api를 통합하고 2차로 레포지토리를 바꾸는 식으로 점진적으로 api를 통합해나갈 계획이다.


하다보니 느낀점이
내가 아직 신입인데 올바른 방향으로 가고 있나 걱정되기도 하고 이상한 코드는 이상하게 짜인 이유가 있었고 다시 만들어도 비슷한 레일을 따라가는거 같긴 하다..?
혹시 예상치 못한 버그가 생길까봐 조심스레 개선하고 있는 중이다.

그래도 컨택스트 파악 안된 코드를 다음 사람에게 물려주고 싶진 않아서 노력하고 있다..🔥

반응형

+ Recent posts