Architecture
어떻게 연결되어 있나
구성도 한 장으로 시작해서, 그 선들이 실제로 무엇을 주고받는지까지 내려간다.
Topics
신호가 향하는 다섯 갈래
브로커를 쓰기로 한 순간 '누구에게 보낼까'는 토픽 설계 문제가 된다. 다섯 갈래로 고정했고, 모든 토픽은 매장 식별자를 반드시 포함한다.
| 범위 | 받는 쪽 | 쓰임 |
|---|---|---|
| 매장 전체 | 그 매장의 모든 단말 | 메뉴 변경 같은 매장 단위 공지 |
| 손님 단말 | 매장의 오더앱 전부 | 주문 상태 갱신, 원격 업데이트 |
| 점원 단말 | 마스터앱 | 신규 주문·호출 알림, 테이블 변경 |
| 테이블 | 그 테이블의 오더앱 | 해당 테이블 주문 내역 갱신 |
| 개별 단말 | 단말 하나 | 재시작, 업데이트 지시 |
단말을 가리키는 토픽에는 DB의 기본 키가 아니라 단말이 스스로 갖고 있는 코드를 쓴다. 한 번 기본 키로 발행했다가 단말이 구독한 토픽과 어긋나 아무것도 도착하지 않는 일이 있었고, 지금은 그 이유가 코드에 주석으로 남아 있다.
Events
오가는 신호
페이로드는 이벤트 이름 한 줄이다. 데이터를 싣지 않는다.
주문 생성
→ 점원 단말
소리로 알린다
주문 변경
→ 점원 · 손님 · 테이블
조용히 화면만 갱신
호출 생성
→ 점원 단말
소리로 알린다
메뉴 변경
→ 매장 전체
가격·품절·배지 즉시 반영
앱 재시작
→ 단말 또는 매장
원격으로 앱을 되살린다
업데이트 적용
→ 단말 또는 손님 단말 전체
번들을 받아 즉시 또는 다음 부팅에
배터리 조회
→ 손님 단말 → 점원 단말
점원이 각 테이블 잔량을 본다
'주문이 들어왔다'와 '주문이 바뀌었다'를 굳이 다른 이벤트로 나눈 건 소리 때문이다. 하나로 합치면 상태를 하나 고칠 때마다 매장에 벨이 울린다.
Flows
실제로 이렇게 흐른다
손님이 주문을 넣으면
- 1오더앱이 주문을 REST로 보낸다.
- 2서버가 주문을 저장하고, 점원 단말 토픽에 '주문 생성' 신호를 던진다.
- 3마스터앱이 신호를 받고 주문 목록을 다시 조회한 뒤 소리를 낸다.
- 4점원이 처리하면 이번엔 '주문 변경' 신호가 손님 단말과 테이블 토픽으로 나간다.
신호에는 주문 내용이 실려 있지 않다. 받은 쪽이 다시 조회한다. 메시지 하나를 놓쳐도 데이터가 어긋나지 않게 하려는 것이고, 대신 신호마다 조회가 한 번 더 붙는다.
점원이 메뉴를 품절 처리하면
- 1마스터앱이 변경을 서버에 반영한다.
- 2서버가 매장 전체 토픽에 '메뉴 변경'을 던진다.
- 3모든 오더앱이 메뉴를 다시 불러온다.
- 4장바구니에 이미 담겨 있던 품절 메뉴는 자동으로 빠지고, 손님에게 안내가 뜬다.
품절은 '못 담게 막는 것'만으로 끝나지 않는다. 이미 담아둔 손님이 결제까지 갔다가 실패하는 게 최악이라, 담긴 것까지 되짚어 정리한다.
단말이 살아 있는지 확인하는 법
- 1각 단말이 주기적으로 자기 상태를 서버에 보고한다.
- 2앱 버전, 번들 버전, 접속한 네트워크, 배터리, 마지막 부팅 시각을 담는다.
- 3관리 웹의 단말 목록이 이 값을 그대로 보여준다.
'업데이트를 발행했는데 저 태블릿에 실제로 갔나'를 눈으로 확인할 방법이 필요했다. 배포 파이프라인을 만들고 나서 곧바로 이게 없으면 안 된다는 걸 알게 됐다.
Resilience
흔들려도 티가 안 나게
매장 네트워크는 좋지 않다고 가정하고 만들었다. 다만 여기 있는 건 전부 '흔들림'에 대한 대응이지 '단절'에 대한 대응은 아니다.
지금의 한계
인터넷이 완전히 끊기면 주문은 되지 않는다. 단말이 클라우드 서버에 직접 붙는 구조라, 회선이 죽으면 매장 안에서 자체적으로 주고받을 경로가 없다. 현재 구현은 끊김을 감지해 주문을 실패로 처리하고 손님에게 알리는 데까지다. 매장 로컬 경로를 만드는 것이 다음 작업이고, 아래 항목들은 그 전 단계에 해당한다.
네트워크 상태를 세 단계로 본다
연결됨·느림·끊김을 나눠서 판정한다. 끊긴 것과 느린 건 대응이 달라야 해서다.
이미지와 영상은 받아두고 로컬에서 재생
캐시에 있으면 로컬 파일을, 없으면 내려받아 교체한다. 내려받기에 실패해도 원본 주소로 그냥 재생해서 화면이 비지 않게 한다.
MQTT 발행 실패가 주문을 막지 않는다
신호를 못 보내도 주문 자체는 저장된다. 알림은 부가 기능이지 거래의 일부가 아니다.
옛 앱 + 새 번들 조합을 견딘다
번들만 갱신되고 네이티브는 옛것인 상태가 실제로 생긴다. 네이티브 기능을 부를 때 없을 수도 있다고 가정하고 감싼다.
MQTT 수신은 네이티브 서비스에서
앱 화면이 내려가 있어도 재시작 같은 지시는 받아야 한다. 그래서 수신 담당을 앱 생명주기 바깥에 뒀다.