"outbox 는 신뢰 모델 전체를 object store 하나에 담아: 커밋했고, 배달을 기다리는 것들."
왜 IndexedDB 지, localStorage 가 아니라
내구성 있는 폰 쪽 캡처엔, 저장소 선택이 장식이 아냐. localStorage 는 동기, 문자열 전용, 작아 — 메인 스레드를 막고 이미지 blob 을 못 담아. IndexedDB 가 맞는 도구야: 비동기(UI 를 절대 안 얼려), 트랜잭션 지원(지난 레슨의 원자적 aggregate 가 이거에 기대), 구조화된 객체랑 바이너리 Blob 을 직접 저장해. 앱 재시작을 넘어 이미지 붙은 질문을 안전하게 쥐는 게 일 전부인 클라이언트한테, 그 조합은 있으면 좋은 게 아냐 — 기반이야.
outbox 패턴
핵심 구조는 outbox 야: 캡처된 턴들의 내구성 있는 큐, 각각 sync 상태 태그가 붙은. 메시징 세계의 outbox 패턴을 기기에 적용한 거야. 흐름은 어느 지점에서 끊겨도 안전한 루프야:
- Enqueue: 제출된 턴이 blob 이랑 원자적으로,
pending으로 outbox 에 들어가. - Drain: 워커가
pending턴을 집어 배달을 시도하고,sending으로, 그다음answered나failed로 옮겨. - Resume: 앱 실행 때, 워커가 아직
answered아닌 걸 outbox 에서 다시 훑어 멈춘 데서 이어가.
outbox 가 내구성 있으니까, 이 단계 중 뭐도 이전 게 같은 세션에서 끝났다고 가정 안 해. enqueue 랑 drain 사이에 앱을 닫으면, drain 은 그냥 다음 실행 때 일어나. 큐가 곧 기억이야.
outbox 는 폰의 ground truth
기기에서 outbox 는 서버가 아는 걸 담은 캐시가 아냐 — 서버가 아직 들었든 말든 상관없이, 사용자가 뭘 물었는지의 권위 있는 기록이야. 그래서 몇 시간 오프라인을 살아남을 수 있어: 로컬 상태의 진실 출처로 서버를 필요로 하지 않으니까. (백엔드는 canonical 대화의 진실 출처로 남아 — outbox 는 폰의 내구성 캡처 층, 다른 일이야. 트랙 5 가 그 선을 정확히 그어.)