본문 바로가기
C.W.K.
Stream
Lesson 02 of 04 · published

resolver 하나, 봉투 둘

~12 min · shared-code, divergence, ownership-boundary, design

Level 0신호 없음
0 XP0/32 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"똑같은 부분은 같이 쓰고, 소유가 달라지는 딱 그 자리에서 갈라져. 한 줄 일찍도 한 줄 늦게도 아니고."

가운데는 같이 써

Pippa Go 엔 이미지를 실어 나르는 흐름이 둘 있어. Prompt Macro 는 대화 턴 없이 composer 를 돌리는 유틸리티고, Ask 는 canonical 스트리밍 챗이야. 둘 다 똑같은 상황에서 출발해. 사용자가 이미지를 골랐고, 그게 지금 기기 로컬 staging 에 blob 이랑 메타데이터로 앉아 있는 상황. 그 staged blob 을 실제로 쓸 수 있는 바이트랑 제대로 된 media type 으로 바꾸는 일은 어느 흐름이든 똑같아. 그래서 staged-media resolver 하나에서 한 번만 처리해. 이걸 두 군데에 복사해 두면 전형적인 drift 버그가 나거든. 인코딩이랑 방향이랑 media type 감지가 두 복사본에서 조금씩 어긋나기 시작해.

여기서부터는 일부러 갈라져

바이트를 다 풀어냈으면 이제 두 흐름이 갈라져. 그 갈라짐은 우연이 아니라 원칙에 따른 거고. 소유 경계에서 갈라지는 거야. 이미지가 어떻게 되느냐는 그 결과를 누가 갖느냐에 달렸으니까.

  • Prompt Macro 는 utility 봉투로. Macro 는 대화가 아니라 apply-free 유틸리티야. 그래서 이미지가 가벼운 utility 봉투에 실려. 인라인으로 인코딩한 바이트로. canonical 상태를 안 만드는 one-shot 호출한테 딱 맞는 모양이지.
  • Ask 는 canonical 봉투로. Ask 는 진짜 대화 턴을 만드니까 이미지가 canonical 첨부로 업로드되고 chat 메시지는 그걸 참조해. canonical 턴의 나머지랑 똑같이 백엔드가 갖는 거야.

풀어낸 바이트는 같은데 봉투가 둘인 거야. Macro 의 결과는 일회용이고 Ask 의 결과는 canonical 이니까. 이 갈라짐은 군더더기가 아냐. 소유 모델이 코드 밖으로 비쳐 보이는 거지.

규칙은 하나야. 경계까지 같이 가고, 경계에서 갈라지기

이건 어디든 가져다 쓸 수 있는 설계 감각이야. 두 흐름이 앞부분은 같고 뒤에서 달라지면, 같은 부분을 컴포넌트 하나로 빼내고 요구사항이 실제로 달라지는 지점에서 갈라지게 해. 여기선 결과 이미지를 누가 갖느냐가 그 지점이고. 덜 공유하면 resolver 를 두 벌 갖게 되고(drift), 더 공유하면 Macro 랑 Ask 를 둘 다 안 맞는 봉투 하나에 욱여넣게 돼(leaky abstraction). staged-media resolver 는 그 선을 제대로 그었어. '진짜 바이트랑 media type 을 손에 넣었다' 까지는 공통이고, 그 바이트가 어디 사느냐부터는 흐름마다 다른 거야.

Code

공유 resolver, 그다음 소유 경계에서 봉투 둘·typescript
// 공유: 두 흐름에 동일 — staged blob -> 진짜 바이트 + media type.
async function resolveStagedImage(localId: string): Promise<ResolvedImage> {
  const blob = await readStagedBlob(localId);
  return { bytes: await blob.arrayBuffer(), mediaType: blob.type };
}

// Prompt Macro: 일회용 유틸리티 호출 -> 인라인 utility 봉투.
async function macroEnvelope(img: ResolvedImage) {
  return { inlineData: toBase64(img.bytes), mediaType: img.mediaType };
}

// Ask: canonical 턴 -> canonical 첨부로 업로드, 참조.
async function askEnvelope(convId: string, img: ResolvedImage) {
  const ref = await uploadCanonicalAttachment(convId, img); // 백엔드 소유
  return { attachmentRef: ref };
}

// resolver 하나가 둘 다 먹여. 소유가 다른 딱 그 데서 갈라져.

External links

Exercise

앞 처리는 같은데 결과물이 다른 기능 두 개를 골라 봐. 같은 문서에서 출발하는 '초안 내보내기' 랑 '바로 공개' 같은 거. 요구사항이 갈라지는 정확한 경계를 짚어. 같은 일은 함수 하나로 묶고, 갈라지는 꼬리는 둘로 빼고. 그다음 확인해. 경계까지만 딱 공유했어, 아니면 경계를 넘어서 만능 경로 하나로 묶어 버렸어?
Hint
맞는 이음매는 두 흐름이 똑같은 결과를 원하는 마지막 지점이야. 거기까진 전부 공유하고, 요구사항이 처음 달라지는 데서 쪼개. 공유 함수 안에 'if flow === A' 가 들어 있으면 한 걸음 더 멀리까지 공유한 거고.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.