본문으로 바로가기

프로젝트 회고

category company/culture 2024. 5. 25. 14:08
728x90

OpenAPI 개발의 Break Point가 생겼습니다.
해당 프로젝트가 어떻게 흘러왔는지 앞으로는 어떻게 하는게 좋을까 회고해보려 합니다.

1. 프로젝트 시작

처음엔 모든 팀원이 참여하는 프로젝트로 어떤 기술 스택을 적용할지 다 같이 조사하고 결정하였지만 실제 개발은 조사에 참여하지 않았던 10년차 개발자(UI/UX 위주의 경험, 중간정도 참여)와 신입 공채분 그리고 제가 프론트엔드 개발을 담당하게 됐습니다. 이 프로젝트에 참여하게 된 계기는 이전 디자인 시스템 개발 경험을 토대로 이 프로젝트에 디자인 시스템 이슈와 개선사항을 전달하고 사용 경험을 얻고자 신뢰하는 팀장님께서 좋은 자리를 만들어 주셨습니다.
해당 시점에 FE Ops가 가능한 리소스가 없었기에, 이전 조금이라도 경험 있던 제가 담당하게 됐고 TurboRepo + PNPM + LightHouse CI + Tekton 을 배포하기 위해서 Dockerfile과 lighthouse-trigger-yaml 그리고 k8s values 등을 만들고 도메인 신청 등 사내에 필요한 워크플로우를 적용해서 배포했습니다.
 

2. 문제

리소스

모든게 수월하진 않았습니다. 리소스가 너무 적었고, 10년차 개발자분은 다른 프로젝트를 맡고 있어서 도움을 주는 역할이었습니다. 초기에 가장 힘들었던 부분은 신입 개발자분의  스스로 개발할 수 있는 역량과 주도성 낮아 도움을 주면서 개발하는거였고, 꾸준한 PR 리뷰와 방향 설정에 도움을 주더라도 성장 속도가 더뎌서 고민이 많이 됐습니다. 특히나 CI 구성이나 인증부분을 개발할 땐 화면에 보여지지 않는 부분을 작업할 땐 10년차 개발자분이 도움을 주지 않을 땐 프로젝트가 멈춰있다는 느낌을 받았습니다. Tech Dept도 꾸준히 남겨 시간 될 때마다 이슈를 분배하였고, 초기에 검색을 state로만 관리하려 하여 startTransition에 관한 도큐먼트를 레퍼런스로 제공하여 단계별로 Tech Dept를 해결해 나가며 아주 다행히도 시간이 조금 걸렸지만 멋지게 성장하였고, 현재는 둘이서 주도적으로 개발하고 있습니다.
 

환경

이제까진 AWS, Vercel, Firebase, Github Actions, Jenkins로 너무 쉽게 CI/CD를 구성할 수 있었습니다. 내부 buildpack에 구현되지 않은 부분도 많았고 Dockerfile을 만들어서 배포해도 테스트하는 과정 또한 쉽지 않았습니다. JWT를 이용한 next-auth provider 또한 Google, Github 로그인과 같은게 아니라 사내 인증을 이용해야하는데 알아내는 과정이 어려웠습니다.
사내 디자인 시스템을 적용하기로 했을 때 Turborepo packages/ui에 해당 패키지만 변경할 수 있도록 구현했고 추후 headless/ui, antd/deisgn, react/chart, react-hook-form등 여러 라이브러리에 종속되지 않도록 인터페이스를 정의하여 개발했음에도 라이브러리에서 필요한 내용을 상속받아 사용하였던게 타입 변환이 필요하거나, loop를 돌며 데이터 형태를 바꿔줘야하였고 초기에 변경하기로 결정했거나 적게 사용한 부분은 직접 변경하였습니다. 사내 디자인 시스템 지원이 중단되어 버그나 추가기능 개발이 막혀서 많은 컴포넌트들을 변경하는게 쉽지 않았습니다.
 

차트 & 로그

이전 회사에서 다뤘을 땐 S3와 Redshift 그리고 Athena를 이용하여 마케팅 관련 데이터를 보여줬었고, 초기 화면에 필요한 데이터(오늘 날짜) Redis에 데이터 캐싱 하여 프론트 측면에선 예쁘게 보일 라이브러리를 활용하여 (ApexChart) 제공하는게 다였습니다. 이번엔 Granfana와 비슷하게 15초 단위로 쪼개서 저장된 데이터 로그를 보여줘야 했기에, 데이터 양이 방대했고 초기 ant-design/plots를 사용하다 대용량 데이터에 최적화하여 표현할 수 있는 uplot(Grafana에서도 사용중)로 바꿨습니다. 뿐만 아니라 데이터 양/Client Width에 맞게 데이터의 간격을 주어 데이터의 양을 조절하여 요청하였는데 생각치 못한 부분인데다 해당 부분에 집중하기엔 다른 변경점도 신경써야하는게 쉽지 않았습니다. ant-chart를 사용할 땐 series 별로 구분할 수 있었는데 데이터 소스가 다르게 내려오는 경우가 있었습니다. 백엔드분과 어느쪽에서 해결해야하나 이야기를 했고, 프론트에서 데이터 구조를 해시구조로 변환하여 timeseries키로 하여 두 가지 소스를 (예를 들면, 데이터가 401, 402, 403, 4xx와 같은 구조로 구분되어 내려왔는데 환경별로) 변환해서 해결했습니다. 

실시간 로그는 SSE로 구현했는데, GraphQL로 전달되지 않아 Mocking이 기존 작업과 달라 순차적으로 다른 작업을 하다가 마지막에 연결하는 과정이 있었습니다. 차트와 로그 부분은 cursor 베이스의 intersection observer를 통한 inifinite scrolling으로 페이징을 구현했습니다.
 

데이터

백엔드에서 내려주는 데이터가 버그가 발생하거나 리팩토링을 하는 과정에서, 프론트 로컬환경 또한 dev를 같이 바라봤기에 시작 파생 데이터가 문제가 생긴 경우 프로젝트를 진행하는데 어려움을 겪었습니다. 이건 MSW를 도입하였고 graphql-codegen에 faker와 함께 적용하여 쿼리를 작성할때 자동으로 데이터를 만들었고 env에 변수를 하나 두어 키고 끌 수 있도록 제공하여 해결하였습니다.
 

env

초기엔 dockerfile이 아닌 사내에서 제공하는 빌드팩을 사용하여 env를 사용하다가 dockerfile을 제공하는 방식으로 변경되어 하나의 이미지로 여러 환경에서 사용할 수 있도록 개발하기 위해 import-meta-env를 적용하였습니다.
 

URL/Zustand

화면 마다 요청해야하는 데이터는 zustand/persist를 사용하여 localstorage에 저장하여 사용하였다가, URL에 해당 id를 포함시키지 않았더니 주소를 복사하고 붙여넣을 때 접근할 수 없는 문제가 발생하였습니다. 또한 웹스토리지를 사용하다보니 선택된 정보를 바꿔 스토리지가 바뀌면 모든 탭의 선택 값이 같이 변경됐습니다. 이후 URL 체계를 변경하여 segment와 searchParmas를 토대로 조회하고 추가적으로 필요한 데이터는 GraphQL을 통해 요청(fetch-policy는 cache-first)하고, zustand in memory에 저장하여 사용하였습니다.
 

DTO

서버데이터는 GraphQL codegen에서 타입을 만들어서 제공해줬는데, 초기엔 그냥 사용하다가 타입 정의가 불편하고 맞지 않는 부분이 생겨서 클라이언트 타입을 따로 정의하기로 했다. 이 과정에서 고민이 됐는데, 배달의 민족에서 발간한 책을 보다가 class형태로 만들어서 서버 데이터를 인스턴스화하여 변환하여 사용한게 소개되어 해당 형태로 적용하였습니다. 우선 따로 DTO를 관리할 수 있어서 데이터 형태만 관리할 수 있어서 책임을 분리할 수 있었다. 중간에 런타임 타입에러를 잡을 수 있는 zod도 함께 적용하였습니다. react-hook-form에 resolver도 제공해주고 타입도 만들어줘서 parse하여 사용하였습니다.
 

GraphQL

graphql-codegen에서 use*Query, use*SuspenseQuery, use*LazyQuery 그리고 query 자체인 *Document와 *Variables를 제공해줬고, RSC와 SSR에서 다르게 사용해야 했습니다. @apollo/experimental-nextjs-app-support 패키지를 사용했었고 서버 컴포넌트에서 제공할 수 있다면 getClient().query({query, variables})를 통해 데이터를 내려줬습니다. 초기엔 공통으로 사용하기 위해 만들었던 쿼리들을 Fragment를 사용하여 CoreFragment를 공통으로 사용하여 페이지 별로 필요한 데이터만 요청하였고 fetchPolicy와 필요에 따라 직접 캐시를 업데이트 하는 등에 작업 또한 같이 진행됐습니다. RSC에 대한 이해가 적어 초기엔 모두 use*Query로만 작성하였다가 점차적으로 RSC로 데이터를 이동해왔습니다. 물론 아직까지도 검색, 페이징, 정렬과 같은 부분에서 초기엔 RSC에서 데이터를 갖고오고 그 다음 variables에 변경이 발생한다면 useLazyQuery를 통해 가져오는 방법 또한 어떨지 tech dept에 남아있습니다.
 

프로젝트 구조

초기엔 여러번 바꼈고 현재는 어느정도 정착된 상태입니다. 우선 Next.js App Router 가이드 문서를 따라 작성하였고 (routes) 안에는 해당 페이지 라우팅에 관련된 layout.tsx, error.tsx, loading.tsx, page.tsx와 함께 하위 페이지들을 포함했고, 기타 해당 도메인에만 사용하는건 _(underscore)를 이용하여 private한 부분이고 routing이 안된다는걸 구분하여 사용했습니다.
interactors안에는 비지니스 로직이 포함되도록, presentors에는 UI만 포함되도록 구현하였습니다. react가 아닌 Next/Router와 같은 형태도 최대한 랩핑하여 사용하였고, 종속되지 않기 위해 만들었으나 팀장님께선 디렉토리 자체가 Next.js App Router에 종속적이라 Page Router로 변경하거나 버전이 올라가서 변경점이 크게 발생할 수 있는 위험도를 이야기해주셨습니다.
- app
   - (routes)
      - page A
         - _utils/_components/_hooks/_interactors/_presentors
         - loading.tsx
         - (routes)
            - page B
   - _utils/_components/_hooks/
당근에서 있던 비슷한 고민
 

3. 나의 평가

사실 이번 프로젝트가 흘러가며, 팀 뿐만 아니라 실에서 좋은 평가를 받고 있다는 느낌을 강하게 받았습니다. 개발자끼리 소통에서는 변화되는 기획과 백엔드 스키마 변경에 유연하게 대처해줬고, GraphQL을 Hasura Engine을 통해 만들었는데 Schema Relation이 필요할 때 백엔드 개발자 도움 없이 연결하는 방법을 그리고 Validation 에러 등 자주 발생하지만 한 번 익혀두면 같이 해결할 수 있는 점들을 프론트엔드 개발자에게 전달하기 위해 위키에 작성였습니다.
이전 프로젝트에선 팀원으로서 묵묵히 일하는 모습을 보여줬다면, FE Ops와 리딩하며 신입 개발자의 성장과 다른 개발자분이 가장 잘할 수 있는 부분을 찾아서 리소스를 분배하여 적은 m/m로 개발하였고 프로젝트 셋업부터 Tech Dept와 개발에서도 개인적으로도 많은 성장을 이룰 수 있었던 프로젝트라고 생각합니다.