네이티브 개발자와 함께하는 피드 서비스 설계하기
지난 4년전에 Vingle재직 당시 Texture를 기반으로 피드 퍼포먼스를 개선 했던 적이 있었어요. 당시 기억과 추억들 되돌아보니 감회가 새롭네요 :]
초기에 당근마켓 피드는 UITableView기반으로 cell size를 계산해서 캐싱 및 Kingfisher를 이용하여 이미지를 fetching해오는 방식으로 구성되어있었고, cell size 계산처리나 UI복잡도가 높은 편은 아니라서 유저들이 사용하는데는 불편함은 없었을거에요.
하지만, 당근마켓에서의 피드는 중고거래 뿐만 아니라 다양한 서비스들을 제공하고 담아가야하는게 목표였고, UI의 컴포넌트화와 재사용성 그리고 국가분기등 모든 복잡한 과정들을 단순하고 빠르게 Develop해 나감과 동시에 유저에게 보다 더 나은 쾌적한 피드 사용감을 제공하기 위해서 Texture(AsyncDisplayKit)을 도입하게 되었어요.
왜 Texture가 좋은지는 이전 article에서도 다뤄진 적이있어서 자세한 건 Texture gitbook을 참고하시면 많은 도움이 되실꺼에요 :]
이번에는 2020년 6월에 시작했던 당근마켓 피드 Re-architecting과정에서 경험을 기반으로한 소소한 설계 팁들을 공유해볼려해요. 물론, 네이티브 개발자 관점에서요!
따라서, 다음과 같은 것들을 이야기해볼려해요.
- Pagination: 페이징 구조를 어떻게 설계 했는지에 대해서 다뤄볼려고해요.
- Overview: 피드에 보여지는 UI 컴포넌트를 어떻게 Generic하게 구성했는지 다뤄볼려해요.
- Logging: 피드에 보여지는 요소들에 대한 click, impression 등과 같은 기타 행위들에 대한 logging 을 가볍게 다뤄볼려해요.
Pagination
초기에 제가 마주하게된 당근마켓 피드의 형태는 가장 원시적?인 상태의 피드였어요.
다음 페이지를 불러오기 위해서 마지막 게시글의 작성된 시간을 기점으로 이전에 피드에 올라오게 된 게시글들을 N개씩 불러오는 형태였고, 빈 배열형태로 게시글들을 받게 되면 피드 페이징이 끝났다는 것을 의미 했었어요.
// GET articles?published_at=”~~~”
{
"articles": [
{
"id" : 1,
"published_at": "~~~~~~"
},
....
]
}
그 이후 시간일 흘러, 머신러닝 및 사용자의 행동으로 부터 유의미한 데이터를 얻어내기 위해서 필요한 meta-data를 피드에서 내려주고 이것을 네이티브에서 다음 페이지를 호출 할 때 by-pass해주는 형태로 고도화가 진행하게 됐어요.
// GET articles?published_at=”~~~”&some_meta=”~~~”
{
"articles": [
{
"id" : 1,
"published_at": "~~~~~~"
},
....
],
"meta": { .... 필요한 정보들 .... }
}
피드를 설계해보신 분들은 이미 알고 계시겠지만, 위와 같이 필요에 따른 지속적인 meta-data 추가와 마지막 게시글의 published_at 을 기반으로한 Pagination에서 생기는 문제점들은 다음과 같았어요.
- 휴먼에러: 다음 페이징 방식에 대한 제약을 개발자들이 인지하고 통일된 컨벤션으로 개발을 했어야했지만, 사람이 늘어남에 따라 컨벤션 공유와 인지에 대해서 흐려지기 시작하면서 meta-data에 페이징 정보를 넣는 케이스가 생기되었고, 이로인해 피드를 설계 확장함에 있어서 페이지네이션이 되지 않는 버그를 만들기도 했어요.
- 버져닝: meta-data에 필요한 정보들이 추가 될 때마다 네이티브에서 동시에 작업을 해야하고 배포를 해야하는 일이 생겨 Version 관리에 리소스가 생길 수 밖에 없었어요.
따라서, 버져닝없이 지속적으로 확장가능한 페이징 정보 추가 가능하게 설계하기 휴먼에러 없는 페이지네이션 설계하기 위와 같은 니즈들을 충족하기 위해서 피드 서비스 백엔드 개발자와 함께 작업을 진행하게된 안드로이드 개발자와 함께 머리를 굴린(속된 말로 뇌더링)결과 다음과 같은 설계가 탄생했어요.
1안
next(다음페이지)또는 prev(이전페이지)에 내려온 string값의 empty 유무로 다음/이전 페이지 유무를 식별해요. 받은 next/prev를 다음/이전 페이지를 호출 시 end-point에 query string을 붙여서 호출해요. (예: https://feed?next=some_data=234&publish_at=234553.33)
{
"pagination" : {
"prev": null,
"next": "some_data=234&publish_at=234553.33"
}
}
하지만, 위의 형태로 취하게 되었을 때 기존 안드로이드에서 설계해둔 network layer에서 위의 설계를 기반으로 하기에는 많은 희생을 할 수 밖에 없어서 피드 서비스 백앤드 개발자와 함께 다시 머리를 굴린 결과 다음과 같은 결과가 나오게 됐어요.
2안
기존 1안에서 host까지 붙여서 제공하기 (기존 메커니즘과는 동일)
{
"pagination" : {
"prev": null,
"next": "http://feed?some_data=234&publish_at=234553.33"
}
}
1안과의 큰차이점이라면 query string으로 붙여야하는 불필요한 작업 필요없이 피드서비스에서 받은 full url을 호출해가면서 다음/이전 페이지를 불러올 수 있는 이점도 있을 뿐더러 안드로이드 개발자도 약간의 기반작업은 해야했지만 어찌되었든 모두가 행복한 결론에 다다르게 되었어요. 1안도 나쁜건 아니지만 2안은 네이티브 코드내에서 불필요한 작업이 줄어들어 훨씬 더 awesome하고 동시에 boiler-plate도 줄일 수가 있었어요.
Overview
현재 당근마켓에서 유저들이 보고 있는 피드에는 중고거래 뿐만 아니라 다양한 UI들을 접하고 있어요. 동네인증이라던가 공지사항, 동네홍보 게시글등등이 있죠. 당근마켓 피드팀에서는 이 피드에서 나오는 컴포넌트들을 통틀어서 Overview라고 정의했고, 각 역할 및 행위에 따라서 다양한 Overview 들을 서빙하고 유연하고 확장가능하게 설계를 했어요.
Overview에 대해서는 어떻게 PM들과 커뮤니케이션을 하고 있으며, 피드 서비스에서 서빙 그리고 네이티브 개발자들이 최종적으로 설계를 하고 세상에 보내는지 간단히 적어볼려고해요.
사업팀에서 유저들에게 보여주고 싶은 다양한 형태의 Overview들 당근마켓 사업팀에서는 각 분야에서 뛰어나신 PM분들과 개발자분들이 살고있어요. 거기서 각자 개발한 다양한 서비스를 피드에서 보여주고 싶지만, N개의 사업팀 서비스들만큼 N개의 Overview를 만드는 것은 상당히 비효율적일 뿐더러, 관리포인트만 늘어나기 때문에 피드에 웹뷰?를 이용해서 보여주자는 의견도 있었는데… 이 글을 읽고 계시는 iOS개발자분들은 아시겠지만 피드 중간에 WKWebview를 품은 cell이 들어간다는건… (생략) 아무튼, 사업팀에서 하고있는 모든 서비스들을 녹여낼 수 있는 Overview를 만들기 위해서 다음과 같은 과정을 거쳤어요.
최소스펙공유
모든 사업팀 PM분들이 만족할 수 있는 Generic한 Overview가 필요했고 따라서 GeneralBusinessOverview 라는 이름으로 작명을 한 후에 안드로이드 개발자와 함께 GBO(GeneralBusinessOverview)에서 나올 수 있는 UI형태들과 유저가 취할 수 있는 행동들(overflow 클릭 후 나오는 action sheet 동작들과 action item들)에 대해서 overlap처리한 후에 사업팀 PM분들께 공유드렸어요. 이 후 사업팀 PM분들은 다 같이 모여서 브레인 스토밍을 진행하게 되는데…
추가 스펙 수렴 및 트레이드 오프
GBO가 사업팀 내에서 공유되는 자원이라는 사실을 인지를 하고 계셨던 사업팀 PM분들은 overlap처리된 GBO를 기반으로 추가적인 요구사항을 수렴해서 네이티브 개발자들에게 제안하게 되었고, 구현 가능 범위에 대해서 논의한 후에 우주?최강 당근마켓 프로덕트 디자이너 분들의 손을 거쳐서 아름다운 형태를 갖추게 되었어요. 이후 피드 서비스 팀에서는 각 사업팀 백엔드 개발자들이 이용할 수 있도록 즉, 서빙을 할 수 있도록 기반작업을 함과 동시에 네이티브 개발자들은 Spec을 바탕으로 generic하게 GBO를 만들어서 오랜시간 걸리지 않고 세상 밖에 안정적으로 안착시켰어요 :] 이후에도 추가적인 스펙이 있으면 이미 generic하게 설계된 GBO이기에 디자이너, PM, 개발자 동료들 모두가 generic하게 고민을 해가면서 붙이고 있어요. 이렇게 제품개발 속도를 잘 유지해가면서 유기적으로 기획-디자인-설계를 함과 동시에 그 과정속에서 누구나 자유롭게 의견을 제시하면서 개선해 나가는 과정이 있었기에 좋은 제품으로 세상 밖에 나올 수 있었어요.
Logging
피드에 보여지는 요소들에 대한 click, impression 등과 같은 기타 행위들을 역시 네이티브에서 하드코딩하지 않고 pagination과 유사하게 서버사이드에서 언제든 스펙을 변경할 수 있도록, 유저가 할 수 있는 행위들(클릭, Impression, 특정 overview 숨기기 등등)을 최소 스펙으로 나열을 한 후 네이티브에서 최소한 행동해야하는 행위를 정리를 해서 설계를 했어요. 그리고 전달해야할 이벤트 이름들과 Parameters는 피드 서비스에서 내려주는 형태로 구성되었어요. 그 과정에서 초기에 아쉬웠던 점은 아무래도 firebase를 사용하다보니 firebase에 의존적으로 설계를 했었지만, 이후에 당근마켓 자체적으로 구축된 awesome한 BI가 추가되면서 현재는 네이티브 코드상에서 훨씬 단순해진 형태로 유지하고 있어요.
마무리
서버개발자가 생각하는 좋은 설계와 네이티브 개발자들이 생각하는 좋은 설계는 서로 다를 수도 있기 때문에 커뮤니케이션 과정에서 충돌이 있을 수도 있어요. 마치 금성에서 온 여자와 화성에서온 남자처럼요. 이러한 피드 서비스를 개발한다는 것은 네이티브 개발자 입장에서는 서버개발자의 생각을 이해 할 수 있는 과정이며, 반대로 서버개발자들은 네이티브 개발자들의 생각을 이해 할 수 있는 즉, 소통하고 서로를 이해 할 수 있는 서비스를 개발할 수 있는 좋은 기회이자 행운이라고 생각해요.