~12 min · contracts, generalization, indirection, api-design
Level 0불 꺼진 작업장
0 XP0/35 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
필드 둘, 근데 오늘 일하는 건 하나뿐
이음매를 건너가는 건 거의 아무것도 아냐. 파이프라인 이름 하나, 그리고 종류 + 식별자 모양의 포인터 하나. 그게 전부야. 포인터는 이게 어떤 종류의 물건인지랑 그중 어느 거인지를 말해. 물건 자체는 일부러 안 실어.
파이프라인은 하나뿐이고, 그 하나가 먹는 재료 종류도 하나뿐이야. 그러니 종류 필드는 지금 아무 일도 안 해. 식별자만으로도 완벽하게 돌아가. 그런데도 있어. 그 이유가 이 강의의 진짜 내용이야. "아직 필요 없는 필드를 넣었다"는 보통 실수인데, 여기선 아니거든.
구현 말고 계약을 일반화해
안 보이는 미래를 위해 미리 지을 수 있는 게 두 가지야. 구현을 일반화할 수 있어. 파이프라인 프레임워크, 플러그인 레지스트리, 추상 단계 인터페이스를 지어서 두 번째 파이프라인 붙이기를 쉽게 만드는 거. 이게 비싼 쪽 추측이고, 이 작업장 교리가 대놓고 금지해. 존재하는 하나를 위해 구체적으로 지어라. 가상의 두 번째를 겨냥해 설계한 프레임워크는 추측에 맞춰 재단된 거니까.
아니면 계약을 일반화할 수 있어. 경계를 건너가는 것의 모양 말이야. 이건 필드 하나 값이야. 그리고 구현이랑 달리, 계약은 나중에 바꾸기가 비싸. 바꾸려면 양쪽을 동시에 맞춰야 하거든. 짓기 싸고 바꾸기 비싼 쪽을 미리 일반화하고, 짓기 비싸고 바꾸기 싼 쪽은 구체적으로 두는 것. 이건 거의 일반 규칙에 가까워.
종류 필드가 사주는 것
포인터에 종류가 붙어 있으면, 미래의 파이프라인이 완전히 다른 종류의 원재료를 가리킬 수 있어. 일기 항목이든, 에세이든, 살아 있는 앱이든. 그래도 이음매는 하나도 안 바뀌어. 부르는 쪽은 계속 포인터를 보내. 받는 쪽이 종류로 분기해. 아무도 뭘 다시 협상 안 해.
조용한 이득이 하나 더 있어. 종류가 붙으면 기록 안에서 payload 가 스스로를 설명해. 반년 뒤에 옛 요청을 읽어도, 그때 어떤 파이프라인이 현역이었는지 몰라도 뭘 가리켰는지 알 수 있어. 로그에 남은 타입 없는 식별자는 수수께끼야. 그 뜻이 그동안 바뀌어버린 코드 안에만 살았거든.
간접 참조가 재료를 도구보다 오래 살게 해. 포인터는 대화를 안 실어. 주소를 실어. 그래서 재료는 그걸 제대로 보관하는 시스템에 남고, 실행 시점에 새로 길어 올려지고, 누가 내년에 재생할지도 모르는 요청 안에 낡은 사본으로 박혀 있지 않아.
Code
이음매를 건너가는 payload 전부·json
{
"pipeline": "pippalog-episode",
"pointer": { "kind": "conversation", "id": "<opaque-identifier>" },
"why": "the correction beat in the middle is the whole episode"
}
// Note what is NOT here: the conversation text, any rendering
// instruction, any duration, any format. The receiving side
// dereferences the pointer itself, at run time, from the system
// that owns that material.
종류로 분기 — 타입을 읽는 유일한 자리·python
def dereference(pointer: dict) -> Material:
"""One place knows how to turn a pointer into material.
Adding a pipeline adds a branch here. It does NOT change the
contract, the emitter, or anything on the calling side.
"""
kind = pointer["kind"]
if kind == "conversation":
return fetch_conversation(pointer["id"])
raise ValueError(f"no fetcher registered for kind: {kind!r}")
# Deliberately NOT built: a plugin registry, an abstract Fetcher
# base class, a config-driven dispatch table. One kind exists.
# A raise on the unknown case is the honest amount of structure.
네 시스템에서 맨 식별자만 싣고 다니는 메시지나 이벤트, 작업 payload 를 하나 찾아봐. 반년 뒤에 읽는 사람이 소비하는 쪽 코드를 안 보고 해석하려면 뭐가 필요한지 물어봐. 그다음 그걸 답해줄 제일 작은 타입 구분자를 넣고, 소비자가 모르는 값에 기본값으로 넘어가지 말고 예외를 던지게 해. payload 의 의미 중 얼마나 많은 부분이 그동안 소비자 안에만 있었는지 보일 거야.
Hint
제일 티 나는 건 소비자가 '어느 큐로 왔는지'로 타입을 추론하는 경우야. 생산자가 둘이 되거나 누가 메시지를 엉뚱한 데로 재생하기 전까진 잘 돌아가. 그다음엔 payload 가 자기를 변호 못 해. 자기가 뭐였는지를 한 번도 안 싣고 다녔으니까.
Progress
Progress is local-only — sign in to sync across devices.