이 매거진은 여섯 달 동안 같은 문장을 반복해 적었다.
오프라인·캐시 동작 — 아직 열려 있는 줄
(2026년 1월을 돌아보며에서 그대로 옮긴 것이다.) 이번 편이 그 줄을 지운다.
왜 이게 남아 있었나
만들기 귀찮아서가 아니다. 오프라인은 시험하기가 까다롭다. 연결이 있는 상태를 시험하는 건 쉽다 — 돌려서 되면 된다. 없는 상태를 시험하려면 없게 만들어야 하고, "없다"는 상태가 여러 종류다.
이 샘플은 그중 가장 나쁜 두 가지만 다룬다.
- 보낼 데가 없다. 그런데 손님은 앞에 서 있다.
- 보냈는데 대답이 안 왔다. 도착했는지 아닌지 패드는 알 방법이 없다.
둘은 반대 방향의 실패로 이어진다. 첫 번째를 안 다루면 주문이 사라지고, 두 번째를 안 다루면 두 번 계산된다. 큐를 넣어서 첫 번째만 해결하면 두 번째가 새로 생긴다 — 그게 이 편이 하고 싶은 말이다.
계산대가 있을 때

till: connected
online: took 1, delivered 1, till has 1 (3.5k won)
평범하다. 커피 한 잔 눌렀고, 계산대가 받았다.
계산대가 사라진다
till: process gone — the pad is now on its own
프로세스를 죽였다. 푸드트럭이라면 핫스팟이 끊긴 것이고, 패드 입장에서는 구별되지 않는다 — 보낸 게 답을 안 받는다는 사실만 같다.
그리고 주문 세 건을 더 받는다.

offline: took pad1-002 (sandwich) — nothing was sent
offline: took pad1-003 (coffee) — nothing was sent
offline: took pad1-004 (juice) — nothing was sent
offline: pad still took 3 orders, 3 waiting, ids pad1-002 pad1-003 pad1-004
화면이 빨간 건 정직하기 위해서다. 안 보내지고 있다는 걸 숨기면 그건 오프라인 지원이 아니라 오프라인 은폐다. 주문을 계속 받되, 몇 건이 아직 안 갔는지 사람이 볼 수 있어야 한다.
아웃박스가 전부다
이 샘플의 답은 클래스 하나다. 주문 패드는 계산대를 직접 부르지 않는다.
/// The order pad never calls the till directly. It writes into here, and here
/// tries to deliver. That one indirection is what lets the person behind the
/// counter keep working while the till is unreachable, and it is also what
/// makes the failure honest: the pad can show how many orders are still
/// undelivered, because the outbox knows.
그 한 겹이 두 가지를 동시에 준다. 계속 일할 수 있는 것과, 몇 건이 밀렸는지 말할 수 있는 것.
규칙은 두 개뿐이다.
① id는 여기서, 첫 시도 전에 만든다.
final p = Pending('$device-${(++_minted).toString().padLeft(3, '0')}', item);
계산대가 id를 붙이면 재전송이 새 주문이 된다. 그러면 손님이 두 번 계산된다. 재시도가 안전하려면 무엇을 재시도하는지가 재시도 전에 정해져 있어야 한다.
② 순서대로 보내고, 첫 실패에서 멈춘다.
} catch (_) {
// Still ours. Leave it at the front and stop — the ones behind it are
// newer and must not overtake.
break;
}
건너뛰고 뒤엣것을 보내면 장부가 아침을 잘못 적는다. 12시 주문이 11시 주문보다 먼저 찍힌 장부는 그냥 틀린 장부다.
그리고 큐는 파일에 있다.
/// Where the queue lives between app launches. A queue that only exists in
/// memory is not a queue — closing the lid is the same as losing the orders.
final File spoolFile;