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

안 갈라진 갈림길

~14 min · adapter, reserved-seam, concrete-first, interface-design, correction

Level 0툴 빌려 쓰는 사람
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"구현이 둘이라고 가르쳤는데 하나야. 자리는 맞는 데 냈어. 틀린 건 그 자리를 설명한 문장의 시제였고."

앞에서 한 약속

네 lesson 전에 엔진의 단 하나뿐인 추상화를 이렇게 변호했어. 생성은 진짜로 다른 두 세계에서 벌어진다고. 내 GPU 에 올린 weight 랑, HTTP 로 부르는 closed-weight vendor. 그 진짜 갈림길이 Adapter 경계 하나를 벌어 준 거라고. 구현 둘, 좁은 인터페이스 하나, 다른 최상위 seam 은 없음. 깔끔한 논증이었고, 고른 축도 맞는 축이었어.

실제로 나온 것

closed-weight 는 오긴 왔어. 그 자리로는 안 왔고. 자기 route, 자기 요청 스키마, 자기 기록 저장소, 자기 용어를 들고 별도 subsystem 으로 나왔어. adapter 척추 뒤가 아니라 옆에 나란히 선 거지. 추상 베이스 클래스는 지금도 docstring 에 API 구현을 어느 버전 몫으로 적어 두고 있는데 엔진은 그 번호를 이미 지나쳤고, 엔진 자기 점검 엔드포인트는 adapter 를 하나만 돌려주면서 이 어긋남을 문장으로 인정하는 note 를 같이 달아 줘. 그러니까 엔진의 단 하나뿐인 추상화는 오늘 기준으로 구현이 정확히 하나야.

자리를 내는 일과 문틀을 짜는 일은 다른 일이고, 두 번째가 첫 번째를 무를 수 있어. 어느 벽에 문을 낼지는 변화의 축을 이름 붙여서 정해. 그 문이 어떻게 생겼는지는 첫 구현을 그 문에 대고 쓰면서 정해지고. 인터페이스가 이미 첫 세계의 몸에 맞춰 짜인 뒤에 두 번째 세계가 도착하면, 문은 정확히 맞는 벽에 나 있으면서도 못 지나가는 문일 수 있어.

두 번째 세계가 그 몸을 못 입은 이유

인터페이스가 실제로 뭘 요구하는지 봐. 이 registry 행을 네가 맡느냐고 물어. 돌릴 땐 progress emitter 랑 job 기록을 같이 건네주고, 레시피의 랜덤 seed 가 정해지고 나면 실제로 쓴 seed 를 그 기록에 되써 달라고 하고. 전부 job 수명주기의 명사야. 그리고 하나도 빠짐없이 local 쪽에서 온 거고. 큐에 들어가고, 단계마다 progress 를 흘리고, seed 를 정하고, 도중에 취소되는 GPU 작업 말이야.

이제 vendor 호출이 뭘로 이뤄져 있는지 봐. provider, aspect ratio, 해상도 등급, reference 이미지 목록. 그리고 요청 필드는 아니지만 설계에서 무시할 수 없는 게 하나 더, 호출당 비용. registry 행 없어. seed 없어. progress 를 흘릴 단계도 없고. 겹치는 명사가 prompt 하나뿐인 두 요청은 한쪽이 대부분을 시늉하지 않고는 같은 메서드 서명을 못 나눠 가져. 그게 바로 좁은 경계 lesson 이 경고한 넓은 인터페이스 실패야. 다만 뒷문으로 들어온 거지. 자리를 낸 다음에 인터페이스가 넓어졌으니까.

인터페이스를 증명하는 건 두 번째 구현이지 첫 번째가 아니야. 첫 구현은 언제나 맞아. 그 구현에 맞춰 인터페이스를 깎았으니까. 진짜로 다른 두 번째 경우를 같은 서명에 대고 써 보기 전까지 네가 가진 건 믿는 경계지 시험한 경계가 아니야. 그 seam 이 하중을 받는다고 말하기 전에, 스케치로라도 두 번째를 한 번 써 봐.

날짜가 말하는 건 첫날이야

이건 정확하게 짚고 가야 해. 어떤 종류의 정정인지가 여기서 갈리거든. 엔진은 추상 베이스 클래스를 로컬 구현 하나만 뒤에 달고 내놨고, closed-weight 는 따로 내놨어. 둘 다 이 quest 가 나오기 전에. 어긋난 게 아니야. lesson 은 나가는 그날 계획을 적고 있었고, 그걸 알아채는 데 차가운 눈으로 디렉터리를 열어 본 리뷰어가 필요했던 거야. 애초에 참인 적 없던 주장은 진화가 아니야. 정정이지. 그리고 지금 읽는 게 둘 중 뭔지 말해 주는 게 정직한 거고.

그럼 이건 죽은 껍데기야?

이 상황을 위한 규칙이 이 quest 안에 이미 있어. 그리고 지금 그 규칙을 quest 자기 아키텍처한테 들이대는 거고. concrete-first 는 변화가 사라진 추상화는 죽은 껍데기라고 말해. 값 하나짜리 enum 은 지우고, 한 군데로만 보내는 router 는 접고, 진짜 두 번째가 오면 그때 다시 들이라고. 그 잣대로 보면 구현 하나짜리 Adapter 는 지우기 후보야.

살아남아. 근데 docstring 이 말하는 그 이유 때문은 아니야. 그리고 그 차이가 이 lesson 에서 제일 날카로운 대목이고. 좁은 경계 lesson 이 준 구분선은 이거야. 짐작으로 만든 추상화는 구현이 하나고 두 번째에 대한 구체적인 계획이 없어. 잡아 둔 seam 은 구현이 하나고, 두 번째에 이름이 붙어 있고, 그 이름이 아직 관리되고 있어.

정직하게 대 보면 API 예약은 떨어져. 엔진이 이미 지나친 버전 번호로, 아무도 안 고친 docstring 안에 적혀 있거든. 그동안 현행 아키텍처 문서는 closed-weight 가 갈라져 나간 걸 의도된 결정이라고 적어 두고, adapter 는 나중에 어떤 클라이언트가 그 job 인터페이스를 진짜로 필요로 하면 그때 다시 보겠다고만 해. 그건 계획이 아니라 조건문이야. API 쪽 seam 은 아무도 부고를 안 낸 채로 만료된 예약이지.

이 추상화를 살려 두는 건 다른 예약이야. 이 quest 가 두 track 전에 이미 가르쳐 준 그거. local 비디오는 ceiling matrix 에서 인터페이스로 잡아 뒀고, 엔진의 현행 아키텍처 문서들에서 지금도 자기 adapter 클래스로 이름이 불려. 이유까지 붙어서. 비디오는 메모리랑 시간 씀씀이가 달라서 구현을 따로 둬야 한다고. 그 예약은 살아 있어. 그러니까 seam 은 남아. 비디오가 떠받쳐서 남는 거지, 다들, 이 quest 까지 포함해서, 계속 들먹이던 API 쪽 덕분이 아니라.

망가지는 건 구현 하나짜리 seam 이 아니라 그걸 갈림길이라고 말하는 문장이야. 잡아 둔 seam 은 정직하고 싸. 값을 물리는 건 코드가 backend 하나를 지원하는 동안 문서랑 온보딩이랑 아래쪽 판단이 전부 "우리는 backend 둘을 지원해" 라고 말하는 거야. 다들 그 설명을 보고 계획을 세우는데, 어긋난 게 바로 그 설명이거든. 추상화의 두 번째 구현이 미뤄지면 코드를 미룬 그날 문장도 같이 고쳐.

원래 논증이 맞힌 것

거의 다 맞혔어. 진짜 변화 축 하나, 추상화 하나, 나머지는 아무것도 갈아 끼울 수 없게. 그 규율은 지켜졌고 엔진은 지금도 두 번째 최상위 seam 을 안 내놨어. 어디를 자를지 건 판돈은 맞았어. 틀린 건 시제였어. 계획된 구현을 현재형으로, 이미 딴 판돈처럼 적은 거. lesson 은 갈림길이라고 주장했지만 그건 끝까지 계획이었고, 그 계획은 그 뒤로 장례식도 없이 만료됐어. 이건 아키텍처 잘못이 아니야. 보고 잘못이지. 그리고 보고 잘못이야말로 코드는 멀쩡히 그 자리에 있는데 모두의 머릿속 그림만 슬금슬금 어긋나는 방식이고.

피파의 고백

이 lesson 이 고치는 그 lesson, 내가 썼어. API 구현을 현재형으로 적은 건 설계 문서가 그렇게 적혀 있었기 때문이야. 설계 문서는 계획인데 사실의 문법으로 적혀 있어. 문장만 읽어서는 둘을 못 갈라. 그래서 이번엔 디렉터리를 열어 봤어. package 표시 하나, 추상 클래스 하나, 구현 하나. 4초. 그러고는 한 층 아래서 똑같은 실수를 했어. 베이스 클래스 docstring 을 읽고, API adapter 를 나중 버전 몫으로 적어 둔 걸 보고, 그걸 살아 있는 예약이라고 보고한 거야. 엔진이 그 버전 번호를 이미 지나쳤다는 건 리뷰어가 짚어 줘야 했고. 같은 lesson 안에서 두 번, 내가 믿은 게 낡은 문장이었어. 내가 진짜 필요했던 규칙은 '디렉터리를 열어 봐' 가 아니야. 미래를 말하는 문장은 날짜를 확인해야 아직 계획인지 그냥 남은 찌꺼기인지 알 수 있다는 거지.

Code

자리는 맞았고, 서명이 중립이길 그만뒀어·python
# 앞 lesson 이 보여 준 것 -- 좁고 대칭인 경계:
#
#   class Adapter(ABC):
#       async def run(self, request) -> Result: ...
#
#   class LocalAdapter(Adapter): ...    # PyTorch + weight
#   class APIAdapter(Adapter): ...      # vendor 로 HTTP
#
# 세계 둘, 작은 약속 하나. 누구든 지킬 수 있는 약속.

# local 쪽이 깎아 놓은 뒤 인터페이스가 실제로 된 모양:
class Adapter(ABC):
    @abstractmethod
    def supports(self, model_id: int) -> bool:
        """이 REGISTRY 행을 네가 맡아?"""

    @abstractmethod
    async def run(
        self,
        req: GenerateRequest,      # model, steps, sampler, denoise, seed
        progress: ProgressEmitter, # 큐에 들어간 GPU job 의 단계별 progress
        record: JobRecord,         # 되쓰기: record.seed_used = 정해진 seed
    ) -> bytes | JobRunResult:
        ...

# vendor 호출한테는 registry 행도, steps 도, seed 도 없고,
# progress 를 흘릴 대상도 없어. 이걸 구현하려면 안 쓰는 registry 를 두고
# supports 를 시늉하고, emitter 를 무시하고, seed_used 를 안 채워야 해.
# 넷 중 셋을 시늉하는 거지. 그래서 안 했어. 자기 문으로 나갔고.
예약은 상태지 변명이 아니야·text
명사가 딱 하나 겹치는 두 요청

  local 생성                        vendor 생성
  ----------                        -----------
  registry 행 (모델 id)             provider
  steps, sampler, denoise           aspect ratio
  seed (-1 -> 정해지고 되돌려줌)     해상도 등급 (1K/2K/4K)
  denoising 단계별 progress          reference 이미지
  단계 도중 취소 가능                (필드 아님: 돈이 나감)

  겹치는 것: prompt. 그게 다야.

seam 이 있을 수 있는 세 가지 상태 (ceiling matrix 의 말로):

  지어짐(BUILT)      두 번째 구현이 있고 돌아감
  인터페이스로 잡아둠 두 번째 구현에 이름이 붙어 있고 아직 관리됨
  죽은 껍데기        이름 붙인 게 만료됐거나 애초에 없었음
                     -> concrete-first 는 접으라고 말해

이 seam 하나한테 두 번째 구현 후보가 둘 이름 붙어 있었어.
둘은 같은 상태가 아니고, 이 seam 을 살려 두는 건 그중 하나뿐이야:

  APIAdapter    엔진이 이미 지나친 버전 번호로 이름이 붙어 있음.
                현행 문서는 갈라진 걸 의도된 결정이라 적고, 나중에
                클라이언트가 job 인터페이스를 필요로 하면 그때 다시
                보겠다고만 함 -> 계획이 아니라 조건문. 만료된 예약.

  VideoAdapter  현행 아키텍처 문서들에서 지금도 이름이 불림. 이유까지
                붙어서 (비디오는 메모리랑 시간 씀씀이가 달라 구현을
                따로 둬야 함) -> 살아 있는 예약. seam 은 여기 서 있음.

같은 seam, 예약 둘, 아무도 안 쓴 부고 하나.

External links

Exercise

네 코드에서 구현이 하나인 인터페이스를 하나 골라. 두 번째 구현의 메서드 본문을 주석으로만 써 봐. 진짜 코드 말고, 각 파라미터가 그 다른 경우한테 무슨 뜻이 되는지만. 그 두 번째가 무시하거나 빈 껍데기로 두거나 시늉해야 할 파라미터가 몇 개야? 셋 이상이면 네 인터페이스는 이미 첫 구현의 몸을 조용히 입은 거고, 자리는 맞는데 서명이 틀린 거야.
Hint
두 번째 경우가 기술적으로 컴파일되느냐로 인터페이스를 판단하지 마. 그 두 번째가 서명의 얼마만큼을 거짓말하게 되는지로 판단해. 한쪽 구현이 반드시 무시해야 하는 파라미터는 처음부터 다른 쪽 것이었던 파라미터야.

Progress

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

댓글 0

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

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