리액트 네이티브 로 글로벌 서비스를 개발하여 운영하는 김희규입니다. 해외 유저를 대상으로 서비스를 개발한 건 저도 처음인데요. 만타(Manta)는 웹툰 구독 서비스로, 5개월간 개발해 2020년 11월 북미 시장에 출시했습니다. 여타 웹툰 서비스는 많지만, 구독 모델을 도입한 건 웹툰 시장에서 최초였어요.
수많은 실험을 통해 빠르게 서비스를 개발하고, 안정적으로 기능을 추가하며 비즈니스 목표를 달성한 것은 리액트 네이티브를 사용했기 때문이라고 생각합니다. 리액트 네이티브로 Android, iOS 앱 개발을 하며 겪은 경험과 기술적인 노하우를 나누고자 합니다.
크로스플랫폼을 위한 기술 스택
리액트 네이티브를 메인 기술로 선택한 건 크로스플랫폼을 쓰기로 했기 때문입니다. 적은 개발자 수로 빠르고 효율적으로 앱(Android, iOS) 개발을 해야 하는 상황이었거든요. 리액트 네이티브 말고도 Flutter나 Ionic을 고려했는데요. 다른 대안보다도 리액트 네이티브를 사용하는 개발자가 많았습니다. 활용할 수 있는 라이브러리도 많았어요.
만타(Manta)의 웹 또한 리액트로 만들었는데요. 모바일 앱에 리액트 네이티브를 쓰는 것보다는 쉬운 결정이었습니다. 리액트는 매우 안정적이기 때문입니다. 대안으로는 Vue와 Svelte가 있었어요. 최근 Svelte를 사용해봤을때 개발 경험이 좋았지만, 얼마 되지 않은 기술이라 사용 예시가 많이 없었습니다. 결국 안정성을 고려하여 리액트를 선택했습니다.
리액트 네이티브의 양면
리액트 네이티브가 나온 지 벌써 6년이 되었죠. 오랜 기간 업데이트를 거치며 성숙해지긴 했지만, 여전히 잘 동작하지 않아 불편한 것도 있습니다. 기본으로 제공되는 모듈 중에서도 부족한 게 꽤 있는데요. 이를테면 FlatList나 ScrollView를 통해 스크롤이나 애니메이션 처리를 할 때, 원하는 대로 동작하지 않는 경우가 많았습니다. 특히나 웹툰은 스크롤링으로 감상하는 콘텐츠이기에 스크롤 동작이 중요하거든요. 결국 만타(Manta) 앱 개발하면서 직접 커스터마이징을 해야 했습니다.
반면 서비스를 빠르게 개발하고 안정화시킬 수 있었던 것도 리액트 네이티브를 사용했기 때문입니다. 덕분에 비즈니스 목표도 빨리 달성하고, Android, iOS 양쪽에서 사용자를 많이 모을 수 있었습니다.
리액트 네이티브의 장점은 사실상 리액트의 장점입니다. 리액트가 나오기 이전에는 클라이언트 개발할 때 모듈화를 구조화하며 개발하기가 굉장히 어려웠거든요. 하지만 리액트가 처음으로 이 문제를 해결했고, 이후에 나온 클라이언트 라이브러리나 개발 방법론은 리액트의 방법을 기본으로 따릅니다. 리액트 네이티브를 사용할 때도 리액트를 개발하는 방식으로 모듈화 및 구조화를 했습니다. 그래서 코드가 많이 쌓여도 문제가 생기지 않았어요. 새로운 기능들도 안정적으로 붙여나갈 수 있었습니다.
API 요청 상태 관리, Thunk로
API 요청 상태는 리덕스와 redux-thunk만 사용하여 관리합니다. 물론 redux-observable이나 redux-saga 같은 별도의 미들웨어를 사용하는 방안도 검토했는데요. redux-thunk만 써서 API 요청을 처리하는 것이 가장 좋겠다고 판단한 데는 이유가 있습니다.
Saga나 Observable의 경우, 어떤 값이 바뀌면 사이드 이펙트가 생기고 그것을 처리하고 체이닝 하는 방식으로 로직을 처리하죠. 그렇게 하다 보면 값이 언제, 왜 바뀌었는지에 대한 정보가 불명확하여 변화를 추적하기 어렵습니다. 결국 디버깅과 문제 해결이 어려워집니다. 반면 Thunk를 사용하면 정보가 아주 명확하거든요. 물론 Thunk를 처음 쓸 땐 동작 방식이 직관적이지 않기 때문에 이해가 안 갈 수 있습니다. 하지만 익숙해지고 나면 장점이 뚜렷하기에 저는 Thunk만 사용하기를 선호하고 추천합니다.
재사용 모듈・코드
웹과 모바일 앱을 둘 다 리액트로 만들었기 때문에 프로젝트 간에 공유되는 코드가 있습니다. API 요청 상태 관리를 위한 리덕스, Hooks 관련 코드를 재사용합니다.
반면, 서로 다른 클라이언트 간 재사용은 지양합니다. 물론 로그인 처리 및 작품 정보를 불러오는 비즈니스 로직의 경우, 코드가 서로 비슷하긴 합니다. 똑같이 처리할 수 있을 것 같아서 당장 재사용할 수는 있겠죠. 하지만 나중에 로직이 조금씩 달라져 분리하게 된다면, 분리하는 작업이 오히려 더 큰 일이 될 수 있습니다.
재사용 가능한 모듈을 만들어서 관리하고 있습니다. API 요청에 처리에 대한 로직은 다 똑같기 때문입니다. react-query와 닮은 모듈을 만들어 쓰는데요. 리덕스, redux-thunk, 그리고 Hook을 기반으로 직접 만들어서 사용하고 있습니다. react-query와 다른 점은 상태를 전역적으로도, 지역적으로도 관리할 수 있다는 것입니다.
프로젝트 디렉터리 구조 설계
프로젝트를 하다 보면 디렉터리 구조를 어떻게 설계할지 참 고민이 많지요. 모바일 앱 프로젝트의 경우, 가장 중요한 디렉터리는 components와 ducks입니다. components는 리액트 UI 모듈을 모두 구조화하여 관리하는 디렉터리고요. ducks는 리덕스 관련 코드를 Ducks 패턴으로 관리하는 디렉터리입니다.
Ducks 패턴은 리듀서와 액션을 하나의 파일에 작성하는 구조입니다. 물론 요즘은 Redux Toolkit을 사용해 리덕스 관련 코드를 전보다 쉽게 관리하고 있긴 합니다. 하지만 Redux Toolkit을 쓰기 이전부터 Ducks 패턴을 사용해왔기 때문에, 만타(Manta)에선 ducks 디렉터리에 관련 코드를 작성하고 있습니다.
components 디렉터리에는 ‘Atomic 패턴’이라는 유명한 패턴이 있습니다. 5단계로 나누어 구조화하는 게 가장 잘 알려져 있는데요. 만타(Manta) 팀은 5단계가 너무 많다고 판단해 3단계로 쪼개서 구조화를 하고 있습니다.
가장 작은 단위는 atom입니다. 여기에는 단순한 버튼, 체크박스처럼 컴포넌트 하나가 단순한 한 가지 기능을 담당하는 경우엔 atoms 디렉터리에 저장합니다. 반대로 가장 큰 단위는 page인데요. 다양한 앱 화면의 코드를 pages 디렉터리에 저장합니다. 물론 앱 개발에는 pages보다 screens라는 이름이 더 적합합니다. 하지만 웹에선 pages, 앱에선 screens를 쓰면 헷갈릴 수 있기 때문에 pages라는 이름으로 통일해서 쓰고 있습니다. 마지막으로 blocks 디렉터리에 저장하기도 합니다. atom도 아니고, page도 아니지만 여러 화면에서 공통으로 사용할 수 있는 경우입니다.
각 컴포넌트를 만들 때, 컴포넌트 이름과 동일한 디렉터리를 만듭니다. 그 안에 있는 index.tsx 파일이 해당 화면의 최상위 리액트 컴포넌트를 작성합니다. 그리고 스타일은 모두 styles.ts 파일에 작성합니다. 스타일 관련 코드는 styled-components/native 를 사용해 관리하고 있습니다.
컴포넌트를 작성할 때는 UI 담당 컴포넌트와 비즈니스 로직 및 API 요청을 분리하는 Presentational and Container 패턴을 사용하는데요. 컨테이너 컴포넌트는 index.tsx 에 작성하고, UI를 담당하는 컴포넌트들은 이 디렉터리에 따로 생성합니다.
컴포넌트를 작성하며 재사용되는 로직들을 HOC와 Hooks로 구현하고, 해당 코드들은 hocs와 hooks 디렉터리에 모아서 사용하고 있습니다.
CMS 페이지 개발
CMS 페이지 또한 리액트를 사용하여 만들었습니다. CMS(Contents Management System) 페이지는 앱 서비스 운영팀에서 사용하는 사내 웹사이트입니다. 엔지니어 3명이 앱과 웹, 그리고 CMS까지 만들려고 하다 보니 가장 효율적인 방법을 찾고자 했습니다.
먼저 기획과 디자인을 최소화했습니다. 또 비즈니스 요구사항이 있을 때 개발자가 바로 이해해서 구현할 수 있는 구조로 진행했습니다. UI는 Material UI를 사용했습니다. View가 예쁜 것보다는 작업을 정확히 끝내는 것이 중요하기 때문입니다.
특히 CMS 개발 과정에서 리디북스의 노하우가 큰 도움이 되었는데요. 작년에 출시된 만타와 달리, 리디북스는 10년 이상 콘텐츠 서비스를 만들어 운영한 경험이 있기 때문입니다. 리디북스에서 오랫동안 CMS 업무를 하신 분이 운영 담당자로 합류하셨거든요. 콘텐츠 매니징 가이드를 잘 설명해주셔서 시행착오 없이 수월하게 작업했습니다.
풀스택 개발의 장단점
만타(Manta) 팀에는 프론트엔드, 백엔드, 운영을 통틀어 개발자가 3명 밖에 없습니다. 어떨 때는 백엔드 개발을, 어떨 때는 CMS 페이지 개발을 하는 등 풀스택으로 개발했습니다.
풀스택 개발엔 장단점이 있는데요. 우선 단점부터 얘기하면, 절대적인 업무량이 늘어납니다. 프런트엔드와 백엔드 그리고 운영 업무를 다 처리해야 하니까요. 공부해야 할 것도 해야 할 일도 늘어나요. 만약 특정 도메인이나 기술을 깊이 있게 이해하는 전문가가 되고 싶다면, 풀스택 업무가 걸림돌이 될 수 있습니다. 또한 프런트엔드와 백엔드팀의 일정이 나뉘어있으면 병목현상이 발생하는 경우가 비일비재합니다. 프론트엔드 일정이 지연되면 전체 일정이 같이 지연되고, 백엔드 일이 많으면 함께 기다려야 하니까요.
반면 풀스택 개발의 장점도 큽니다. 나무가 아닌 숲을 보며 더 좋은 솔루션을 생각하고, 결정하고, 적용까지 할 수 있어요. 프론트엔드만 담당하는 개발자에게 백엔드는 사실상 블랙박스입니다. 따라서 문제해결 시 클라이언트에서 해결 가능한 방법밖에 찾지 못합니다. 그러나 만약 백엔드도 할 수 있다면, 문제를 백엔드에서 해결하고 클라이언트는 그대로 유지하는 의사결정을 할 수 있습니다. 무엇보다도 큰 장점은 개발자 개인의 성장입니다. 여러 가지 일을 할 수 있다는 것은 큰 경쟁력이기에 개발 커리어에 좋은 기회가 될 수 있습니다.
당연하지만 중요한 개발 원칙
마지막으로, 당연하지만 중요한 저의 개발 원칙을 소개할게요. 바로 “중복 코드를 쓰면 안 된다'”는 것입니다. 하나의 역할에는 하나의 코드만 있어야지, 여러 군데 흩어져있으면 안 된다는 것인데요. 이 원칙만 잘 지켜도 프로젝트를 진행하며 생기는 많은 문제를 해결할 수 있다고 생각합니다.
물론, ‘당연히 중복코드 안 쓰고 하나 만들어서 재사용하는 게 좋지, 뭐.”라고 생각할 수도 있어요. 하지만 실무를 진행하다 보면 말처럼 명확하지 않은 경우가 많습니다. 그래서 ‘추상화’ 개념 역시 중요합니다. 예를 들어 웹툰 뷰어만 있는데 웹소설 기능을 추가할 때를 가정해볼까요. 그럴 때 뷰어를 따로따로 만들지 않습니다. 공통 기능, 이를테면 스크롤 할 수 있는 형태의 모듈을 잘 만들어 놓으면 나중에 재사용할 수 있으니까요. 그래서 저는 추상화와 공통화를 잘할 방법을 많이 고민합니다.
만타(Manta) 팀은 서비스 최초 개발부터 론칭, 그리고 이후 업데이트, 운영에 이르기까지 몇 안 되는 개발자로 효율적인 프로젝트를 진행해왔습니다. 순조롭게 마일스톤을 달성하여 여기까지 왔는데요. 그 과정에서 리액트와 리액트 네이티브의 역할은 절대적이었습니다. 이 경험이 다른 분들께 도움이 되었으면 합니다.
고객과 발맞춰 새로운 콘텐츠 경험을 선보이는
리디와 함께할 당신을 기다립니다.