English

장진호

Peter JangProduct Engineer

컬리와 요기요에서 진행한 일곱 가지 프로젝트입니다.

Product Engineer2025.04 ~ 현재

식단 관리 앱 루션에서 앱과 웹, 어드민, 인프라를 넘나들며 진행한 네 가지 프로젝트입니다.

인증 구조: 로그인할 때만 외부 인증을 거칩니다소셜 로그인과 컬리 로그인은 처음 한 번만 외부 인증을 거치고, 그 뒤 모든 요청은 루션 서버가 발급한 JWT 하나로 인증합니다. 전환 전 앱은 게이트웨이가 컬리 세션을 확인하는 기존 경로로 계속 동작합니다. 요소를 가리키면 설명이 보입니다.
루션 JWT 경로 전환 전 앱 경로 로그인과 서버의 외부 호출
앱 안의 토큰 관리: 갱신을 한 곳으로 모았습니다앱 화면과 웹뷰, Android 만보기 백그라운드 프로세스가 같은 로그인 토큰을 씁니다. 여러 곳이 따로 갱신하면 토큰이 엇갈려 로그아웃될 수 있어서, 갱신은 RoutionAPI 한 곳에서 한 번에 하나씩 처리합니다. 만보기 프로세스는 갱신을 앱에 맡기고, 15초 안에 응답이 없을 때만 직접 갱신합니다.
외부 인증소셜 4종, 컬리 SSO 루션 API 서버access, refresh 토큰 발급 인증 UseCase로그인, 가입, 계정 연동 RoutionAPI갱신은 하나씩, 401이면 갱신 후 재시도 세션 저장소토큰, 가입 진행 상태 앱 메인 프로세스 루션 웹 (웹뷰)앱이 넣어 준 토큰 사용 Android 만보기갱신은 앱에 맡김, 15초 뒤 직접 소셜 SDK, 컬리 SSO 인증 헤더 직접 갱신 토큰 전달 토큰 전달 갱신 요청
인프라 구성: 파일을 올리는 곳과 사용자에게 보내는 곳을 나눴습니다GitHub Actions는 서명한 번들을 S3에 추가만 하고, 어떤 기기에 어떤 번들을 줄지는 어드민이 KeyValueStore의 포인터로 정합니다. 앱이 업데이트를 확인하면 CloudFront 엣지 함수가 이 포인터를 읽어 줄 파일을 고릅니다. 운영과 스테이징에 같은 구성을 한 벌씩 둡니다. 요소를 가리키면 설정이 보입니다.
발행: GitHub Actions가 파일을 추가 제어: 어드민이 포인터를 바꿈 읽기: 앱이 받아 감
배포 흐름: 릴리스 하나가 사용자에게 닿기까지릴리스마다 같은 네이티브의 스토어 빌드가 이미 있는지 봅니다. 없으면 스토어 빌드를 만들고, 있으면 네이티브 코드의 지문(fingerprint)이 그 빌드와 같은지 확인한 뒤 OTA 후보로 발행합니다. 후보는 발행만으로는 사용자에게 닿지 않고, 어드민에서 지정 기기부터 넓혀 갑니다.
점진적 배포: 새 번들을 기기 단위로 조금씩 넓힙니다어드민에서 지정 기기부터 시작해 비율을 올리고, 전체 배포로 마칩니다. 기기마다 정해진 값이 비율 안에 드는 기기만 새 번들을 받으므로, 비율을 올려도 이미 받은 기기는 그대로입니다. 문제가 보이면 어느 단계에서든 롤백합니다. 비율을 움직이거나 자동 진행을 눌러 보세요.
새 번들(후보) 기존 번들(안정) 지정 기기(사내 테스터)
새 번들을 받은 기기
0/ 100대
자동 진행 (어드민 프리셋)
릴리스가 바뀌면 대상 기기도 다시 섞입니다
전환 과정: 베타 앱에서 먼저 검증하고 운영 앱으로 옮기는 중입니다사용자가 쓰는 앱을 멈추지 않도록, 사내 테스트용 베타 앱과 스테이징 환경에서 차례로 검증했습니다.
  1. 0빌드 환경의 지문 통일완료
  2. 1베타 앱에 시범 적용완료
  3. 2-1스테이징에서 점진적 배포 검증완료
  4. 2-2베타 앱을 새 인프라로 이전완료
  5. 3운영 앱 전환진행 중
  6. 4기존 유료 서비스(EAS) 정리예정
파이프라인 전체: 30단계 중 사람이 확인하는 곳은 두 곳입니다채운 칸은 AI 세션, 테두리만 있는 칸은 스크립트 검사, 마름모는 사람이 확인하는 곳입니다. 단계를 가리키면 하는 일이 보입니다.
AI 세션 19 스크립트 게이트 9 사람 확인 2 고칠 게 없을 때까지 새 세션으로 다시 검토, 최대 15회
앱 구조: 요청은 아래 레이어로, 화면 이동은 이벤트로화면(View)은 그리기만 하고, 상태와 동작은 ViewModel이, 데이터 요청은 UseCase와 Repository가 맡습니다. ViewModel은 화면을 직접 옮기지 않고 이벤트를 발행하며, 화면이 이를 구독해 이동합니다.
RouteuseAppRouter, 중복 push 차단 View그리기만 담당 ViewModel상태, 액션, Event 발행 UseCaseexecute() 하나, DTO를 모델로 RepositoryDTO 반환, OS별 파일 RoutionAPI토큰, 401 갱신 EventPublisherViewModel이 발행View가 구독해 이동 Store (zustand)전역: DI 상수, persist기능: 화면 간 작성 상태 루션 API 서버 DI Containerinversify로 ViewModel, UseCase, Repository에 주입 구독 발행 갱신

iOS Developer / Platform Engineer2022.05 ~ 2024.09

음식 배달 앱 요기요의 iOS 앱에서 진행한 세 가지 프로젝트입니다.

화면 구성: 한 화면을 컴포넌트로 나눕니다서버는 화면을 이루는 컴포넌트의 정보를 내려 주고, 앱은 그 정보대로 컴포넌트를 그립니다. 파란 상자 하나가 컴포넌트 하나입니다.
성능: 바뀐 셀만 다시 그립니다데이터가 조금만 바뀌어도 화면 전체를 다시 그리던 것을, 바뀐 셀만 다시 그리도록 바꿨습니다. 그 과정에서 생긴 애니메이션 문제는 직접 만든 DataSource로 풀었고, 컴포넌트 데이터는 병렬로 불러옵니다.
구조: 흐름은 한 방향으로, 기능은 딥링크로뷰와 소통하던 Delegate가 얽혀 코드를 이해하기 어려웠고, 서버가 그리는 화면에는 로직을 넣을 수도 없었습니다. 레이어를 나눠 흐름을 한 방향으로 만들고, 기능은 딥링크로 미리 정의해 서버가 호출하게 했습니다.
동적 범위: 기술이 아니라 합의로 정했습니다서버가 어디까지 바꿀 수 있게 할지는 기술만으로 정할 수 없었습니다.
모듈 구조: 기능마다 UI, Domain, Data 모듈을 둡니다기술 블로그 글에 실은 구조입니다. 홈, 검색, 가게 같은 기능이 각각 UI, Domain, Data 모듈을 가집니다.
결과: 빌드와 실행이 빨라지고 충돌이 사라졌습니다
88%CI/CD 빌드 시간 감소
50%콜드 런치 시간 감소
없음프로젝트 파일로 인한 코드 충돌
전송 방식: 모아서 보내고, 못 보낸 로그는 다음 실행 때 보냅니다묶어 보내려고 이벤트를 잠시 모아 두면, 그사이 앱이 종료될 때 로그가 사라집니다. 그래서 모아 둔 이벤트를 메모리뿐 아니라 스토리지에도 저장합니다.