"이미지 없는 질문은 다른 질문이야. 따로 저장될 수 있으면, 따로 망가질 수 있어."
반만 저장된 질문
질문이랑 이미지를 독립된 두 쓰기로 캡처한다고 상상해봐. 텍스트가 저장되고, 그다음 이미지 blob 이 쓰이기 전에 앱이 죽어. 이제 outbox 엔 '이 중에 뭐가 더 선명해?' 가 이미지 없이 들어있어. 마침내 동기화되면, Pippa 는 말 그대로 답할 수 없는 질문을 받아 — 가리키는 대상이 사라졌으니까. 사용자 의도가 조용히 망가진 거야. 전부를 잃어서가 아니라, 나눌 수 없는 하나의 일부를 잃어서.
캡처 먼저는 질문이랑 모든 첨부를 하나의 aggregate 로 다뤄서 이걸 고쳐: 완전히 쓰이거나 아예 안 쓰이는 한 단위. 텍스트는 있는데 이미지는 없는 유효한 중간 상태 같은 건 없어. 턴 전체가 내구성 있거나, 아니면 제출이 아직 안 일어난 거야.
aggregate 는 하나의 일관성 경계
이 단어는 도메인 모델링에서 왔어. aggregate 는 내부적으로 일관돼야 하고 항상 전체로만 수정되는 데이터 뭉치야. Pippa Go 턴의 일관성 규칙은 단순하고 엄격해 — 질문이랑 그게 참조하는 이미지는 떨어지면 무의미해. 그래서 하나의 경계를 공유해:
- 커밋 하나가 질문 텍스트 랑 모든 이미지 blob 이랑 첨부 참조를 함께 써.
- 그 커밋이 완료 못 하면, 아무것도 없음으로 롤백돼 — 고아 텍스트도, 고아 blob 도 없이.
- 아래서, 배달이랑 재시도는 항상 aggregate 전체를 옮기지 조각을 옮기지 않아.
왜 원자성이 '그냥 다시 붙이기'를 이기나
솔깃한 지름길은 조각들을 따로 저장하고 '나중에 화해시키기' 야 — 이미지가 없으면 다시 붙이기. 근데 화해가 버그의 번식지야: 어느 이미지? 어느 질문에? 두 턴이 동시에 날아가는 중이면? 원자적 aggregate 는 이 문제 부류를 통째로 삭제해. 화해가 없어, 화해시킬 불일치의 순간이 애초에 없었으니까. 쓰기 하나, 경계 하나, 의도 하나 — 사용자가 제출 누른 순간부터 통째로 지켜져.