배포된 손님 화면(smart-order-eta.vercel.app)을 휴대폰 크기로 찍었다
매장 찾기
매장 메뉴
옵션 선택
주문 내역
흐름
주문 상태 흐름
PENDING접수 대기
→ACCEPTED수락
→PREPARING조리 중
→READY픽업 대기
→PICKED_UP픽업 완료
PICKED_UP → 스탬프 +1픽업 완료 때 한 번만 적립 — 10개를 모으면 4,500원 할인
진행 중 → CANCELLED주문 취소
연결 끊김 → 다시 연결1 · 2 · 4초 간격으로 3번 다시 연결하고, 그래도 안 되면 3초마다 조회
상태가 바뀌면 그 주문을 구독 중인 화면에 SSE로 바로 보낸다.
문제 해결 과정
주문 상태 실시간 전달 — SSE
문제
출근 시간대 주문이 몰리면 매장 화면과 손님 화면의 조리 상태가 어긋나고, 새로고침해야 바뀐 상태를 볼 수 있었다
해결 — 무엇을 바꿨나
주문별 SSE 연결을 동시성 맵(주문 ID → 연결 목록)에 두고, 구독하자마자 현재 상태를 먼저 보내 다시 연결해도 놓친 변경이 없게 했다. 15초마다 ping으로 중간 프록시가 연결을 끊지 않게 하고, 끝났거나 실패한 연결은 바로 정리했다. 화면은 끊기면 1 · 2 · 4초 간격으로 다시 연결하고, 그래도 안 되면 3초마다 조회한다
결과
네트워크가 흔들려도 손님 화면은 현재 조리 상태를 다시 맞춘다
두 번 눌러도 주문은 하나 — 멱등 키
문제
결제 버튼을 두 번 누르거나 응답이 늦어 다시 보내면 같은 주문이 두 번 생길 수 있었다
해결 — 무엇을 바꿨나
요청마다 멱등 키(X-Idempotency-Key)를 받아 같은 키의 주문이 있으면 그대로 돌려줬다. 동시에 들어와도 DB 유니크 제약이 하나만 남기고, 저장이 충돌하면 먼저 생긴 주문을 다시 읽어 응답한다. 쿠폰 · 스탬프 사용이 겹칠 때도 같은 방식으로 처리했다
결과
같은 키로 몇 번을 보내도 주문은 하나만 생긴다
설계 결정
위치 기반 매장 찾기
Haversine 공식으로 거리를 계산해 가까운 순으로 정렬하고, 커서로 나눠 한 번에 최대 100개까지 준다.
남의 주문 막기
주문 조회 · 실시간 추적은 주문한 회원 본인이나 그 매장 관리자만, 매장 주문 목록 · 상태 변경은 자기 매장 주문만 할 수 있다.
로그아웃 · SMS 인증
로그아웃 시각을 Redis에 남겨 그 전에 발급된 토큰을 막고, SMS 인증번호는 3분 동안만 유효하다.
검증
백엔드 테스트 119개
테스트 파일 13개 — 인증 20 · 메뉴 옵션 16 · 매장 14개 등.
GitHub Actions CI
백엔드 · 프론트엔드 중 바뀐 쪽만 PR마다 돈다 — 백엔드는 빌드와 테스트, 프론트엔드는 린트.