식단 관리 앱 루션에서 앱과 웹, 어드민, 인프라를 넘나들며 진행한 네 가지 프로젝트입니다.
인증 구조: 로그인할 때만 외부 인증을 거칩니다소셜 로그인과 컬리 로그인은 처음 한 번만 외부 인증을 거치고, 그 뒤 모든 요청은 루션 서버가 발급한 JWT 하나로 인증합니다. 전환 전 앱은 게이트웨이가 컬리 세션을 확인하는 기존 경로로 계속 동작합니다. 요소를 가리키면 설명이 보입니다.
루션 JWT 경로전환 전 앱 경로로그인과 서버의 외부 호출
앱 안의 토큰 관리: 갱신을 한 곳으로 모았습니다앱 화면과 웹뷰, Android 만보기 백그라운드 프로세스가 같은 로그인 토큰을 씁니다. 여러 곳이 따로 갱신하면 토큰이 엇갈려 로그아웃될 수 있어서, 갱신은 RoutionAPI 한 곳에서 한 번에 하나씩 처리합니다. 만보기 프로세스는 갱신을 앱에 맡기고, 15초 안에 응답이 없을 때만 직접 갱신합니다.
인프라 구성: 파일을 올리는 곳과 사용자에게 보내는 곳을 나눴습니다GitHub Actions는 서명한 번들을 S3에 추가만 하고, 어떤 기기에 어떤 번들을 줄지는 어드민이 KeyValueStore의 포인터로 정합니다. 앱이 업데이트를 확인하면 CloudFront 엣지 함수가 이 포인터를 읽어 줄 파일을 고릅니다. 운영과 스테이징에 같은 구성을 한 벌씩 둡니다. 요소를 가리키면 설정이 보입니다.
발행: GitHub Actions가 파일을 추가제어: 어드민이 포인터를 바꿈읽기: 앱이 받아 감
배포 흐름: 릴리스 하나가 사용자에게 닿기까지릴리스마다 같은 네이티브의 스토어 빌드가 이미 있는지 봅니다. 없으면 스토어 빌드를 만들고, 있으면 네이티브 코드의 지문(fingerprint)이 그 빌드와 같은지 확인한 뒤 OTA 후보로 발행합니다. 후보는 발행만으로는 사용자에게 닿지 않고, 어드민에서 지정 기기부터 넓혀 갑니다.점진적 배포: 새 번들을 기기 단위로 조금씩 넓힙니다어드민에서 지정 기기부터 시작해 비율을 올리고, 전체 배포로 마칩니다. 기기마다 정해진 값이 비율 안에 드는 기기만 새 번들을 받으므로, 비율을 올려도 이미 받은 기기는 그대로입니다. 문제가 보이면 어느 단계에서든 롤백합니다. 비율을 움직이거나 자동 진행을 눌러 보세요.
새 번들(후보)기존 번들(안정)지정 기기(사내 테스터)
새 번들을 받은 기기
0/ 100대
자동 진행 (어드민 프리셋)
릴리스가 바뀌면 대상 기기도 다시 섞입니다
전환 과정: 베타 앱에서 먼저 검증하고 운영 앱으로 옮기는 중입니다사용자가 쓰는 앱을 멈추지 않도록, 사내 테스트용 베타 앱과 스테이징 환경에서 차례로 검증했습니다.
0빌드 환경의 지문 통일완료
1베타 앱에 시범 적용완료
2-1스테이징에서 점진적 배포 검증완료
2-2베타 앱을 새 인프라로 이전완료
3운영 앱 전환진행 중
4기존 유료 서비스(EAS) 정리예정
파이프라인 전체: 30단계 중 사람이 확인하는 곳은 두 곳입니다채운 칸은 AI 세션, 테두리만 있는 칸은 스크립트 검사, 마름모는 사람이 확인하는 곳입니다. 단계를 가리키면 하는 일이 보입니다.
AI 세션 19스크립트 게이트 9사람 확인 2고칠 게 없을 때까지 새 세션으로 다시 검토, 최대 15회
앱 구조: 요청은 아래 레이어로, 화면 이동은 이벤트로화면(View)은 그리기만 하고, 상태와 동작은 ViewModel이, 데이터 요청은 UseCase와 Repository가 맡습니다. ViewModel은 화면을 직접 옮기지 않고 이벤트를 발행하며, 화면이 이를 구독해 이동합니다.
화면 구성: 한 화면을 컴포넌트로 나눕니다서버는 화면을 이루는 컴포넌트의 정보를 내려 주고, 앱은 그 정보대로 컴포넌트를 그립니다. 파란 상자 하나가 컴포넌트 하나입니다.성능: 바뀐 셀만 다시 그립니다데이터가 조금만 바뀌어도 화면 전체를 다시 그리던 것을, 바뀐 셀만 다시 그리도록 바꿨습니다. 그 과정에서 생긴 애니메이션 문제는 직접 만든 DataSource로 풀었고, 컴포넌트 데이터는 병렬로 불러옵니다.구조: 흐름은 한 방향으로, 기능은 딥링크로뷰와 소통하던 Delegate가 얽혀 코드를 이해하기 어려웠고, 서버가 그리는 화면에는 로직을 넣을 수도 없었습니다. 레이어를 나눠 흐름을 한 방향으로 만들고, 기능은 딥링크로 미리 정의해 서버가 호출하게 했습니다.동적 범위: 기술이 아니라 합의로 정했습니다서버가 어디까지 바꿀 수 있게 할지는 기술만으로 정할 수 없었습니다.
모듈 구조: 기능마다 UI, Domain, Data 모듈을 둡니다기술 블로그 글에 실은 구조입니다. 홈, 검색, 가게 같은 기능이 각각 UI, Domain, Data 모듈을 가집니다.결과: 빌드와 실행이 빨라지고 충돌이 사라졌습니다
88%CI/CD 빌드 시간 감소
50%콜드 런치 시간 감소
없음프로젝트 파일로 인한 코드 충돌
전송 방식: 모아서 보내고, 못 보낸 로그는 다음 실행 때 보냅니다묶어 보내려고 이벤트를 잠시 모아 두면, 그사이 앱이 종료될 때 로그가 사라집니다. 그래서 모아 둔 이벤트를 메모리뿐 아니라 스토리지에도 저장합니다.