추첨으로 선발했던 이번 우아콘.
한 번의 배달을 위해 필요한 모든 기술들 이 테마였던 만큼, 이번엔 정말 테크니컬한 경험들을 많이 들을 수 있겠다는 기대가 있었다.
꽤나 추첨 경쟁이 치열했다고 알고 있다. 다만 뽑는 모수가 적었던 것인지 주변에 당첨된 사람이 거의 없었다. 운좋게 기회가 되어 참석하게 되었는데, 개발자로 일하고 난 뒤 경험해본 첫 컨퍼런스였어서 더욱 두근거리는 마음으로 입장했다. 우아한 형제들에서 주최하는 개발자 행사가 꽤나 많은데, 이번 우아콘 역시 배민다운 개성을 파르나스몰 곳곳에서 느낄 수 있었다.
수많은 인파를 뚫고 첫 번째 세션 장소로 겨우 이동할 수 있었다. 웰컴 굿즈와 이름표를 받고, 호다닥 부스를 구경한 뒤 발표장으로 이동했다. 이전에 우아한 스터디에 참여했던 적이 있어서인지, 익숙한 얼굴들이 많았다. 우형 분들이 정말 많았는데, 특히 개발자 뿐만 아니라 디자이너, PM 등 다양한 직군 분들을 심심찮게 만날 수 있었다. 단순히 기술 공유만 하는 테크 컨퍼런스가 아닌, UX, PM, 일하는 문화, 교육 등 회사와 프로덕트를 지탱하는 모든 분들이 함께 참여한 콘서트 같다는 인상을 받았다.
그렇지만 역시 개발자인지라, 주로 프론트엔드 세션들에 참여하게 되었다. 11시에 시작한 Module Federation 발표로 본격적인 세션이 시작되었다!
프론트엔드 개발의 미래, Module Federation의 적용
마이크로 프론트엔드를 고민하는 단계까지 온 웹 어플리케이션의 역사, 그리고 팀 내 고민들을 짚어주신 부분이 좋았다. 웹의 역사를 보면 점점 브라우저의 성능이 좋아지고 프로덕트가 고도화됨에 따라 어플리케이션이 점점 비대해진다. 복잡도를 견디기 힘든 탓에 FE와 BE가 나뉘어지고, 각각 안에서도 여러 개의 도메인을 각각의 작은 서비스로 나누게 되기 마련이다. 프론트엔드 어플리케이션 역시 그 비대함과 복잡성을 견디기 힘들어지는 시점이 오자, 점점 사이드이펙트의 예측 가능성, 재사용성, 생산성을 고민하기 시작했다. 백엔드의 MSA처럼 프론트엔드에도 Microservice를 도입하자는 개념이 생겨났다.
배민에서도 동일한 고민이 있었다고 한다. 내부 구성원들이 전시 영역을 관리하는 데 필요한 어드민이 수십 가지가 있다고 한다. 커머스 어드민, B마트 어드민, 셀러, 푸드 등. 관리자들은 각각의 어드민에 접속하고, 인증/인가를 개별 관리했다고 한다. 마찬가지로 개발 시에도 각각의 프로덕트를 변경/배포함은 당연했다. 그렇다 보니, 만약 n개의 사업 영역이 확장된다면 개발자는 수십 개의 탭을 띄워서 반복적인 작업/배포를 할 수도 있는 상황이었다. 그렇게 전시 영역을 하나의 프로덕트로 통합하되, 적절히 앱을 분리해서 관리하자는 니즈가 생겨났다. Host Application은 하나만 관리하고, 각각의 영역에서는 각각의 지면만을 관리할 수 있도록 해야 했으며, 런타임에 분절된 앱의 부분들이 자연스럽게 하나의 앱으로 통합되어야 했다.
그렇게 여러 옵션 중, Webpack5의 Module Federation 를 적용하게 되었다고 한다. Module Federation이란? 하나의 앱을 독립적인 배포가 가능한 모듈 단위(Webpack에서의 청크)로 나누어 브라우저 런타임에 합체시키는 개념이다. 컨테이너 빌드를 따로 하므로, 배포에 소요되는 빌드 시간과 영향범위 파악에 유리하다. 간단히 옵션을 적용해본 모습은 예를 들어 다음과 같다.
new ModuleFederationPlugin({
name: 'remote',
filename: 'remoteEntry.js',
remotes: {
SomeRemoteApp: 'remoteApp@http://localhost:3002/remoteEntry.js',
},
exposes: {
'./NavBar': './src/Nav.tsx',
},
shared: {
react: {
singleton: true,
version: '18.2.0',
},
'react-dom': {
singleton: true,
version: '18.2.0',
},
},
}),
웹팩에는 위와 같은 형태로 모듈에 대한 expose (컨테이너가 외부에 노출할 원격 모듈), remote (host app에서 다른 모듈을 참조), shared (여러 컨테이너에서 같이 사용하는 모듈. 런타임에 단 한 번만 인스턴스를 생성한다) 옵션 등을 손쉽게 사용할 수 있다.
Module Federation은 국내외 컨퍼런스에서 슬슬 화두가 되기 시작하는 것 같다. 얼마 전 FEConf 에서도 flex 팀의 Micro Frontends 전환기 라는 발표를 봤었는데, 수많은 서비스를 운영하는 팀에서 유저에게 하나의 앱을 사용하는 느낌을 주면서도, 도메인 별로 독립적인 개발을 하고 싶다는 니즈를 느낄 수 있었다. 다만 Webpack5의 Module Federation은 이점만큼이나 아직도 버전 업데이트가 너무 빈번하고, 발표자님이 짚어주셨던 타입스크립트 지원 문제가 상당히 치명적으로 느껴진다. (호스트 앱이 리모트 앱의 원격 모듈이 어떤 타입인지 알 수 있게끔 세팅하는 과정이 상당히 번거롭게 느껴졌다.) 또 초기 설정, 유지보수를 위해 웹팩, 롤업 등 다양한 번들러에 대한 딥한 지식이 요구되는 점, 레퍼런스가 부족하다는 점이 확실한 러닝 커브로 존재할 것 같다. 언젠가 Module Federation이 보다 안정화되고 큰 조직에서 경험할 수 있길! 여러 모로 재미있게 들은 발표였다.
프론트엔드 모킹환경에 감칠맛 더하기
개인적으로 모킹환경의 필요성에 많이 공감하던 터라 재밌게 들었던 세션. 앞서 이 글에서도 개발 생산성을 위해 MSW를 도입하면 좋은 점들을 소개해본 바 있다. 발표자분 역시 개발 일정에 치이고, 백엔드와 프론트엔드가 동시에 개발을 해야 하는 시점이 꽤나 빈번하게 발생한다는 점에 모킹 환경의 필요성에 대해 말씀해주셨다. 다만 본인과의 다른 점은...
역시 우형답게 발표자님의 팀 내 모킹 환경은 이미 잘 차려진 밥상 수준이었다는 점이다. (기만인 걸까 하하) 모킹 환경이 이미 도입되어 있었고, 여러 종류의 모킹 케이스도 존재했다고 한다. 수많은 준비된 목데이터가 있었고, 테스트코드에서도 다양하게 활용중이었다고 한다. 다만 API 모킹 환경이 axios mock adapter 로 되어 있어 axios에 의존성이 종속된다는 문제가 있었고, 다른 외부 라이브러리에 대한 모킹이 불가능했다. 실제 네트워크 요청이 발생하지 않는 것도 MSW를 도입하게 된 계기가 되었다고 한다.
개선 전: axios mock adapter | 개선: msw 도입 |
|
|
그러나 다음과 같은 한계는 여전했고...
- 모킹 동작을 변경하기 위해서는 필연적으로 코드 레벨 제어((json response를 수정해야 함)가 필요
- 갈수록 늘어나는 Mock Data 관리 부담감 증가
따라서 발표자분은 UI를 통해 API 모킹을 제어할 수 있는 GUI를 만들기 시작하셨다고 한다. API 단위로 목데이터를 1:n 매핑할 수 있는 프리셋 기능을 넣고, 흩어져 있는 API 프리셋을 실제 상황에 맞게 조합할 수 있도록 프로파일링 기능도 넣으셨다. API 인터페이스가 변경됨에 따라 목 데이터를 꾸준히 업데이트하고자, 실제 API를 요청해보고 프리셋 응답값과 비교해서 검증하는 기능도 구현하셨다. 시연 영상을 잠깐 봤는데, 이쯤 되면 라이브러리로 출시해도 손색이 없을 것 같은 느낌이 들었다. 그 밖에도 개인 설정과 프로젝트 설정을 어떻게 격리할 것인지, 테스트코드에서 모킹을 어떻게 제어하고 타입 자동완성을 어떻게 효율적으로 사용할 것인지에 대한 인사이트를 많이 알 수 있었다. MSW를 본따 만드셨다는 MSG는 현재 베타 환경에도 배포가 되어 있다고 한다. 이유는 기획자나 서버 개발자들도 쉽게 사용할 수 있도록 함이다. 어떤 화면에서 어떤 API를 어떻게 사용하는지까지 트래킹이 가능해서, 단순히 개발환경의 모킹 그 이상으로 활용도가 높아 보였다.
발표 직후 질문 시간에 라이브러리 출시 계획은 없는지 여쭤봤는데, 조금 더 디벨롭해보고 출시를 할 수도 있을 것 같다고 하셔서 기대중이다. 이전에 뱅크샐러드에서도 msw를 UI로 조작할 수 있게 세팅해서 QA 시에도 요긴하게 쓴다고 들었던 것 같은데, 사이드 프로젝트로 재밌게 해볼 만한 것 같다.
프론트엔드 상태관리 실전 편 with React Query & Zustand
상태관리가 프론트엔드 개발의 화두가 된 지도 몇 년이 흘렀다. 그 중 상태를 특성에 따라 Client State와 Server State로 나눠 알맞게 관리하자는 흐름이 있다. 발표자분은 팀내 프로젝트의 복잡한 상태를 관리하기 위해 다음과 같은 변화 과정이 있었다고 한다.
Problem | Solution |
컴포넌트 계층을 분리, Props Drilling 하는 방법만으로는 복잡성이 감당이 안 된다. 불필요한 리렌더링도 일어나고. 유지보수성에 더 좋은 방법이 없을까? | Redux, MobX로 우아하게 전역 상태관리하기 |
근데 Redux, MobX 얘네들은 보일러플레이트 코드가 너무 많다. 스토어가 너무 크고 복잡해진다. 상태관리보다 API 호출 코드가 더 많다. 이게 비동기 통신에 적합한 도구인가? | React Query 도입으로 서버 상태는 얘네들이 전담하도록! 데이터 Fetching, 백그라운드 데이터 업데이트, 캐싱과 Key를 통한 전역 상태와 같은 사용 가능. |
근데 점점 컴포넌트 코드가 복잡해진다. 아직까지 사용중인 크고 무거운 MobX도 좀 걸린다. 서버 상태를 분리하게 되었으니, 클라이언트 상태는 좀더 간단하게 써도 될 것 같은데? | 그렇게 Zustand 도입. 좀더 쉽고 간단하게 코드를 작성하고, 컴포넌트 밖으로 로직 분리 가능. |
결국 팀 내 표준 개발 환경을 구축하는 과정에서, 다음과 같은 장점으로 React Query & Zustand 조합을 선택하게 되었다고 한다.
React Query
- Server State 관리
- 리액트 훅같은 간단한 사용법
- API 호출 코드로 비대해진 스토어를 목적에 맞게 분리
- 여러 인터페이스, 옵션을 제공해 적은 코드로 강력한 동작
- 팀 내 도메인들이 서버와 유기적으로 얽혀있으면서, 캐싱 등 고도화된 비동기 호출 전략이 요구되므로 해당 역할에 적합
Zustand
- Client State 관리
- 컴포넌트 밖에서도 상태 변경이 가능
- 사용성이 단순하여 러닝커브, 보일러플레이트 적음 (단, Flux 패턴에 대한 이해와 러닝 커브는 MobX와 동일하다)
- Redux Devtools 확장 프로그램 활용 가능
- 무엇보다도 외부 상태관리 도구의 의존도가 낮은 팀 내 코드와 전역 상태를 최소화하는 팀의 방향성에 적합
그러나 기술 도입이 만사는 아니다. 상태관리의 복잡성을 줄이기 위해서는 무엇보다도 레이어화된 아키텍처가 중요하다는 점을 짚어주셨는데, 실제 현업에서 사용하는 스타일 예시들을 직접 보여주셔서 더 현장감있게 들을 수 있었다.
위와 같이 서버 상태를 관리하는 queries 를 담당하는 레이어는 비즈니스 로직을 처리하는 hook에서 참조될 수 있고, 클라이언트 상태를 관리하는 stores 레이어는 hooks, queries 모두로부터 참조될 수 있다. 컴포넌트에 집중되어 있는 로직을 레이어층에 적절히 책임을 분리해보려는 노력이 느껴졌다. 단일책임원칙은 언제나 어렵고 험난하다. 그래도 컴포넌트 코드 한 줄 건드리지 않고도 기능을 확장하거나 비즈니스 로직을 수정할 수 있다는 쾌감은 누구에게나 즐거운가보다. 262줄에 달하던 컴포넌트 코드를 101줄로 줄이고 뿌듯해하시던 발표자님의 표정이 잊히지 않는다.
배민도 React Native 해요?: 웹개발자들이 만들어 나가는 배민커넥트앱 이야기
배민에는 수 많은 프로덕트들이 있다. 그 중 React Native 로 쓰여지고 유지되는 팀이 딱 하나 있다고 한다. 바로 배민커넥트다.
배민커넥트 앱은 라이더의 배달 수행을 위한 앱인데, 실시간 지도, 사진, QR 인식, 서명, 푸시알림, 외부툴 연동 등... 한눈에 봐도 꽤나 요구사항이 복잡했다. 하드웨어 자원을 적극적으로 사용해야 했던 탓인지 기존 라이더 앱은 네이티브로 쓰여져, iOS 따로 안드로이드 따로 개발을 해야 했다고 한다. 그러나 어느 기업이든 개발의 효율성은 중요했고, 당시 네이티브 앱개발자의 주도로 RN으로 스택을 전환했다. Code Push라는 강력한 업데이트 도구도 있었다. 그리고 시간이 흘러 React 개발자가 팀에 합류하고 나서 든 첫 느낌은 이랬다고 한다.
익숙한 개발도구, 코드 스타일, 스타일링, 로컬 실행환경도 비슷하다. 번들 사이즈를 걱정할 필요도 없는데, 배포까지 쉽다! 앱개발, 이게 되네?
그러나 입사 후 실상은...
우선 RN의 버전이 상당히 오래 되었다고 한다. 기존 Class Component 시절 작성된 리액트 코드는 가독성이 너무 떨어졌다. 뿐만 아니라 네이티브 기능들을 사용하기 위해 커스텀해놓은 iOS/Android 네이티브 코드들은 웹개발자가 관리하거나 수정하기에 너무 어려웠다. (Java가 익숙한 개발자들이 몇몇 있었지만, Objective-C는 그 누구도 따라가기 힘들었다고 한다...) 웹 개발자로서 z-index를 똑같이 줬는데도 안드로이드에서만 안 보이는 문제. 하위 컴포넌트의 이벤트보다 상위 컴포넌트의 이벤트가 먼저 호출되는 현상. 서드파티도 npm install만 해서 끝나는 게 아니라, 네이티브 코드를 직접 고쳐서 integration 해야 한다던가. 환경을 탄다던가. 수많은 대환장파티가 벌어졌다.
리액트 코드로 만들어도 결국은 네이티브 앱이구나라는 깨달음을 느낄 수 있었다고 한다. "한 손 묶고 진행하는 웹개발" 이라는 표현만 봐도 얼마나 많은 고난을 겪으셨을지 짐작이 간다. 본인 역시 iOS 개발을 해 본 경험이 있던 지라, 얼마나 웹개발과 앱개발이 다른지 발표자분의 감정적 호소(?)에 공감하며 들었다. 그렇게 시작된 웹 개발자들의 React Native 리뉴얼 프로젝트에는 다음 내용들이 담겨 있었다.
- React 코드 정리
- TypeScript 적용, 함수 컴포넌트로 포팅, 상태관리 코드 간소화, 레이어 나누기 등
- 1000줄짜리 클래스 컴포넌트의 300줄짜리 componentDidUpdate (배민도 이런 레거시가 있구나..!)
- 그림을 그려서 의존성, 호출 구조 파악. 로직을 재구성해서 hook으로 분리
- React Native
- React Native Upgrade Helper 통해 과감하게 버전업
- 라이브러리 정리. 커스텀 네이티브 코드 줄이기. 사용 안 하는 패키지 털어내기
- 오류도 같이 털어내기. 앱 크래시 많이 줄일 수 있었음
- 지도뷰
- 지도 자동 확대, 축소, 경로, 배달 동선, 픽업 전달지 위치 등을 가진 가장 중요한 정보들
- 국내지도 활용을 위한 RN용 SDK가 별도로 제공되지 않음. 각 OS와 유형별로 직접 제작된 지도 컴포넌트. 이게 쌓이고 쌓여 웹 개발자들이 건들 수 없는 컴포넌트가 됨.
- 네이티브는 표현만, 제어권은 리액트에 두도록 구조 개선. MapView, Marker, Path 등 개별 표현 요소들을 별개의 컴포넌트로 분리
- RN 안에 웹뷰 지면 넣어서 잦은 수정과 배포에 대응하기. 앱의 지면처럼 보이는 웹뷰 구현 및 인터페이스 활용
- Flipper를 통한 디버깅 환경 구성
- 에러 모니터링(Sentry), 스토리북 적용
Native App -> RN -> React 중심의 React Native 형태로 개편하는 과정을 보면서, 정말 고생 많으셨겠다는 생각밖에 안 들었다.
???: 크로스페이드 전환 선호 옵션이란 걸 들어보셨나요? iOS 에선 이런 것도 대응해야 한답니다
웹개발자의 RN 도전기, 험난하지만 나름 해볼 만도 하다라는 결론으로 발표가 끝이 났다. 확실히 브라우저라는 울타리에 둘러싸여 있던 웹개발에 비해, 앱은 좀 다른 차원의 야생에 던져지는 느낌일 것 같았다. 개인적으로 해보고싶나 되묻는다면 한번쯤 도전해볼만 하지 않을까 하는 생각은 든다. 웹뷰 이상으로 할 수 있는 것들이 많아지고, 보다 네이티브스러운 표현을 위해선 역시 앱개발만한 게 없으니까.
위 4개 이외에도 꽤 다양한 세션에 참여했다. 프론트엔드 개발자들의 사내 사이드프로젝트 제작기 를 다룬 세션이 있었는데, 배민에서는 이런 사이드프로젝트에도 되게 진심이구나를 느꼈다. 일에서 재미를 느낄 수 있게끔 회사 구성원들이 적극적으로 지원해주는 에너지가 참 좋았다. 또, 마지막 타임에 들었던 '우아한 공방' 이라는 디자인시스템 소개 세션은 단순히 어떻게 고도화했는지에 대한 기술적인 자랑보다도, 다양한 요구사항에 걸맞는 시스템을 만들기 위해 디자이너와 개발자가 어떻게 협업했는지에 대한 이야기를 들을 수 있어서 신선하고 재밌었던 것 같다.
첫 컨퍼런스 참석이라서 그런지 의욕이 앞서서 세션을 무려 8개나 채워서 들었다. 그랬더니 오후 서너시 즈음부터는 슬슬 집중력이 저하되기 시작했다. 파워 I로서 수많은 인파에 기빨렸던 것도 사실이다. 중간에 잠깐 있었던 점심시간은 한 시간보다 조금 여유있게 주어졌지만, 그 복잡한 코엑스 파르나스몰에서 밥 먹을 장소를 탐색하는 것도 굉장히 힘들었다. 같이 간 지인과 파르나스몰 지하를 빙빙 돌면서 어디가 웨이팅 줄이 없으면서도 && 적당히 먹을만한 곳인가 열심히도 찾아다녔다. 다음에 가게 될 컨퍼런스는 부디 발표 너무 빡세게 안 듣고, 이벤트 부스 구경도 좀 하면서 페이스 조절을 해야겠다는 교훈을 얻었다.
그래도 세심한 배민답게, 행사장에는 빈백과 충전기가 비치되어 있는 휴게공간이나 럭키드로우같이 쉬어가는 공간들을 많이 마련해둔 것 같았다. 비록 세션 챙겨듣느라 이벤트 뽑기는 참여하지 못했지만, 소소하게 복주머니 굿즈와 초코바같은 간식거리도 받았다. 키캡과 뱃지, 그리고 부적처럼 슬쩍 소매넣기해둔 채용공고 까지 센스있고 알찼다.
전반적으로 유쾌하고 유익했던 구성이었다. 기술적으로 딥다이브하는 세션들도 있고, 캐주얼하게 어떤 경험을 했는지를 녹여낸 세션들도 있어서 밸런스가 잘 맞았다. + 센스있는 굿즈까지 이번 행사에 많은 공을 들였다는 인상을 많이 받았다. 개발자 행사에 이렇게 진심인 회사가 있다는 걸 보여주려는 것 같기도 했다. 시간이 조금 지나면 유튜브에도 영상이 올라올텐데, 못 들은 세션들을 더 들어보려고 한다. 여러 모로 좋은 경험이었고, 기회가 된다면 내년에 또 가고 싶다.