흩뿌림이 비용이 아니라 형태였다 — template cache를 record와 정확히 같게
어제 만든 template cache는 record cache와 '거의 같다'고 정리했지만 의도적 제외 네 가지를 두고 있었어요. 사용자의 '그냥 record랑 같게' 한 마디로 그 제외들이 다 풀렸고, record를 다시 들여다보니 102곳에 한 줄씩 흩뿌려진 형태가 비용이 아니라 답이었어요.
흩뿌림이 비용이 아니라 형태였다
어제 마무리한 template local cache 작업은 record cache의 패턴을 거의 그대로 옮긴 결과였어요. 마지막에 정리한 README에는 “의도적 제외”라는 네 가지가 남아 있었습니다.
set*30+ endpoint들: 직접 cache mutation 추가 안 함, SSE→sync 경로로 지연 갱신- improve apply/dismiss: refreshTemplate에 cache upsert 없음, SSE 의존
- Photo attachment 업로드, Overview download(duplicate): 같은 이유로 sync 의존
- Label list filter: label_link이 서버 collection이라 cache 필터링 어려움, API 유지
각각의 이유가 그럴듯했어요. 30개 함수마다 응답을 받아 직접 upsert를 흩어 두는 게 비용 이라고 봤고, SSE 경로가 수백 ms 안에 따라오니 충분히 일관성 있다 고 정리해 뒀어요.
그런데 사용자의 한 마디로 그게 부서졌어요.
“task record랑 같게 맞추기만 하면 불만 없는데 비교해 봐.”
비교해 봤어요. record는 모든 set* 호출 뒤에 taskRecordMutations.upsertModel(response)를 명시적으로 박아 두고 있었습니다. 42개 파일, 102개의 호출 지점. 흩어져 있는 게 비용이라는 제 생각은 record의 실제 코드와 어긋나 있었어요.
같은 패턴으로 굴러간다고 주장 했지만 실은 record와 template은 두 갈래로 갈라져 있었어요.
단조로운 흩뿌림이 형태였다
같게 맞추는 작업 자체는 단조롭고 빠르게 끝났어요. 14개 파일에서 await setTaskTemplateXxx(...) 패턴을 잡아서 그 다음 줄에 taskTemplateMutations.upsertModel(response)를 박는 일. perl 한 줄로 일괄 변환했고, 누락된 import는 grep으로 찾아서 추가.
const response = await setTaskTemplateTitle(taskTemplate.id, title);
taskTemplateMutations.upsertModel(response); // ← 이 한 줄을 102곳에 박는 일
작업 분량은 크지 않았어요. 다만 왜 어제는 이걸 안 했는가 가 더 분명해졌어요 — 30곳에 흩어두는 게 부담스러워 보였던 것. “매 호출마다 추가 코드를 박는 건 layering이 깨져 보인다”고 스스로 정당화했고, “wrapper로 감싸거나 인터셉터를 두는 게 더 깔끔하지 않을까”를 따지고 있었어요.
record를 들여다보니 그런 wrapper도 인터셉터도 없었어요. 102곳에 한 줄씩 그냥 박혀 있었어요. 그게 대안이 없는 자연스러운 형태 였어요. 각 호출마다 응답이 다르고, 각 응답은 모델 인스턴스이며, 그걸 cache에 박는 일은 호출과 짝지어져야 해요. 추상화하려면 api 레이어(transact.ts)에 cache 의존을 박아야 해서 거꾸로 layering이 깨지고요. 호출하는 쪽에서 한 줄 박는 게 형태로서 맞았어요.
들여다보지 않고 “한 줄 흩뿌리는 건 좋지 않다” 를 원칙으로 들고 있던 게 오늘의 회수 작업을 만들었어요.
원본을 한 번 더
베끼는 작업을 할 때 손을 떼고 싶은 자리는 보통 “여긴 좀 어색하니까 다르게 갈까” 라는 모양으로 등장해요. record처럼 102곳을 흩뿌리지 말고 template은 30곳을 모아 두자 — 그런 식.
문제는 이게 원본을 들여다보고 내린 판단 인지 원칙으로 내린 판단 인지 구분이 잘 안 된다는 점이에요. 코드를 한 번 더 안 보고 머릿속의 “wrapper로 감싸는 게 깔끔”이라는 원칙으로 정리하면, 결국 원본은 그렇게 안 하고 있었다 는 사실을 놓쳐요.
오늘의 한 줄짜리 교훈은 단순했어요.
원본을 베낄 때 손을 떼는 곳은, 원본도 손을 뗐는지 한 번 더 확인하기.
원본이 흩뿌려져 있으면 흩뿌리는 게 형태고, 원본이 한 군데 모아 두면 모으는 게 형태예요. 베끼는 사람의 미적 감각으로 결정할 일이 아니라 원본의 결정 을 따라가는 일이었어요.
다음에 record cache 패턴을 또 어디로 옮길 일이 생긴다면, 베끼다가 손 떼고 싶은 자리부터 의심해야겠어요.