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

필드는 버텼고, 클래스는 비껴갔어

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

Level 0툴 빌려 쓰는 사람
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"비디오 몫으로 잡아 둔 자리가 둘이었어. 하나는 요청 안의 필드, 하나는 클래스 계층 안의 칸. 비디오가 마침내 왔을 때 필드로는 걸어 들어왔고, 클래스는 돌아서 갔어."

잡아 둔 자리에 손님이 온 날

2026-09-21, 엔진이 local 비디오를 내놨어. open-weight 비디오 계열 셋을 엔진 자기 머신들에서 생성해. 이 quest 는 그날을 두 군데서 기다리고 있었어. ceiling matrix 는 비디오 자리를 요청 계약에 잡아 뒀지. 엔진이 첫 이미지를 뽑던 날부터 video 라고 말할 수 있었던 modality 필드 말이야. 그리고 바로 앞 lesson 은 adapter 척추 전체를 두 번째 예약에 기대 세웠어. 엔진 문서들이 하나뿐인 추상화의 다음 구현으로 이름 불러 온 VideoAdapter. 둘 다 같은 미래에 건 판돈이었는데, 결과는 서로 달랐어.

필드가 한 일

요청 계약은 설계한 그대로 버텼어. 같은 generate 엔드포인트가 이제 이미지 요청이든 비디오 요청이든 받고, modality 값이 본문을 어느 모양으로 읽을지 정해. 비디오 요청은 비디오한테 필요한 걸 들고 와. 프레임 수, 프레임 속도, 그리고 역할이 붙은 reference 들. 첫 프레임, 마지막 프레임, 참고 이미지, 참고 영상, 참고 오디오. 이미지를 부르던 쪽은 한 바이트도 안 바꿨어. 이미지 스키마는 지금도 다른 modality 를 경계에서 돌려보내는데, 이제는 그 값을 '구현 안 됨' 이라고 하는 대신 비디오 요청 쪽을 가리켜 줘. 첫날 한 줄 값이던 필드가 넉 달 뒤 완전히 새로운 종류의 일을 실어 날랐고, 어느 클라이언트도 눈치챌 필요가 없었어.

클래스가 한 일

클래스 계층은 우회당했어. local 비디오를 돌리는 녀석 이름이 심지어 VideoAdapter 야. 잡아 둔 이름을 그대로 가져다 쓴 거지. 그런데 Adapter 를 구현하지 않아. 따로 선 클래스야. local 이미지 adapter 를 협력자로 받아 들고 있는데, 비디오 job 이 메모리를 쓰기 전에 이미지 모델을 비워 내려고 그래. 비디오 계열마다 자기 네이티브 런타임을 자식 프로세스로 띄우고, 그 자식의 프로세스 그룹이 전부 거둬질 때까지 디바이스 문의 표를 쥐고 안 놔. 결과는 비디오 파일이랑 옆에 붙는 기록으로 자기 저장소에 써. 같은 날 저녁엔 업스케일이 자기 modality 를 가진 큐 job 이 되면서 똑같은 모양을 따랐어. 이름은 adapter 인데 Adapter 는 아닌 클래스가 또 하나 생긴 거야. 넉 달 된 추상 베이스 클래스는 지금도 구현이 딱 하나야. 그리고 docstring 은 아직 미래 둘을 부르고 있어. 엔진이 이미 지나친 버전 번호로 적힌 API 구현, 그리고 다른 데로 나가 버린 비디오 구현.

자리는 네가 구현하는 클래스 말고 네가 내놓는 계약에 잡아. 요청 필드는 잡아 두기도 싸고 지키기도 싸. 새 종류가 오면 필드가 열어 둔 문으로 자기 모양을 들고 들어오거든. 클래스 서명은 그걸 대고 처음 쓴 구현이 깎아 놔. 그래서 칸을 잡는 순간 옛 종류의 수명주기까지 통째로 같이 잡혀 버려. 여기서 필드는 첫 진짜 손님을 무사히 받았어. 클래스는 이제 세 번 우회당했고. vendor 경로, 그다음 비디오, 그다음 업스케일.

비디오가 그 서명을 못 입은 이유

앞 lesson 이 인터페이스가 뭘 요구하는지 보여 줬지. 모델 행 하나, progress emitter 하나, 정해진 seed 를 되써 넣을 job 기록 하나. 프로세스 안에서 도는 이미지 job 의 명사들이야. 비디오도 그중 몇 개는 같이 써. 큐에 들어가고, progress 를 알리고, seed 를 정해. 나머지는 뿌리부터 달라. 이미지가 denoise 랑 sampler 를 드는 자리에 비디오 요청은 프레임이랑 역할을 들어. 일은 이 프로세스 말고 별도 프로세스에서 벌어져. 결과물은 이미지 바이트가 아니라 비디오 파일이랑 곁들인 기록이고. 게다가 비디오가 제일 먼저 하는 일이 이미지 모델을 메모리에서 밀어내는 거라서, 이미지 adapter 가 되는 게 아니라 이미지 adapter 를 쥐고 있어야 해. 세 종류의 일이 진짜로 같이 쓰는 건 전부 클래스 아래층에 있어. 큐 하나, 디바이스 문 하나, progress 통로 하나, registry 하나, 그리고 이제 어느 머신에 맡길지 정하는 배정 단계 하나. 엔진이 실제로 공유하는 추상화는 adapter 가 아니라 job 이었던 거야. 큐 하나에 올라가는, 종류가 붙은 요청.

새 종류의 일이 오면, 약속받은 클래스보다 옛 종류랑 뭘 같이 쓰는지부터 봐. 큐, 락, 로그 같은 공용 기반이 대개 진짜 추상화가 줄곧 살던 곳이야. 클래스 계층은 종류끼리 목적지만 같을 때가 아니라 수명주기까지 같을 때만 맞아.

router 에 대한 정정

이 트랙 두 번째 lesson 은 모델의 registry 행을 읽어서 어느 adapter 가 그 모델을 맡는지 알아내는 멍청한 router 를 그렸어. 그건 설계 문서 속 router 였어. 코드에는 엔진이 첫 이미지를 뽑은 날부터 2026-09-21 까지 router 자체가 없었어. job 은 전부 하나뿐인 adapter 로 곧장 갔고, 주인을 적어 둔 registry 행은 한 번도 없었어. router 는 비디오가 올 때 드디어 생겼는데, 그 lesson 이 칭찬한 딱 그 뜻으로 멍청해. 모델마다 갈래를 치지도 않고 이름 패턴도 안 봐. 다만 registry 열이 아니라 요청의 종류를 읽어. 이미지냐, 비디오냐, 업스케일이냐가 돌릴 녀석을 골라. 새 종류의 일이 올 때나 움직이는 축에서 갈라지지, 매주 움직이는 축에서는 안 갈라져.

그사이 다른 축은 데이터가 됐어

adapter 가 기대 선 판돈, 그러니까 생성이 서로 다른 곳에서 벌어진다는 건 결국 맞았어. 서브클래스 모양으로 맞은 게 아니었을 뿐이야. 애초에 자기 문으로 나갔던 closed-weight subsystem 이 엔진의 Pro 작업 흐름으로 자랐어. 이제 그 카탈로그에는 지시로 편집하는 local 모델이 vendor provider 들이랑 나란히 올라가 있어. 카탈로그 행마다 어디서 실행되는지, 어떻게 과금되는지도 적혀 있고. local 모델은 따로 돈을 안 받고, 기본값이 된 Codex provider 는 구독제, vendor API 는 쓴 만큼 크레딧을 깎아. 위치가 열 하나가 된 거야. local 행은 여전히 척추를 지나가. 큐, 배정, local adapter, 경우에 따라 다른 머신까지. 나머지 행은 그 자리에서 바로 돌고. 그러니까 이 추상화가 받아 내려던 변화는 엔진이 내주는 카탈로그가 받아 냈고, 그 변화가 살 거라던 클래스엔 여전히 멤버가 하나야.

그럼 이제 죽은 껍데기야?

좁은 경계 lesson 의 규칙이랑, 마지막 트랙의 '한 군데로만 보내는 router 는 지워' 가 세운 규칙을 정직하게 대 봐. 구현 하나. 이름 붙은 두 번째 후보 둘. 하나는 만료됐고 하나는 다른 클래스로 나갔어. 추상화 뒤에 서 있는, 이름 붙고 관리되는 두 번째는 이제 없어. 이 quest 자기 잣대로 보면 Adapter 베이스 클래스는 이제 지우기 후보야. 엔진은 아직 그 걸음을 안 뗐어. 베이스 클래스도 docstring 도 그대로 있어. 딴 데서 일어난 미래 둘을 기리는 작은 기념비처럼. 이렇게 말하는 건 엔진을 향한 판결이 아니야. 이 트랙이 가르친 점검을 이 트랙 자기 척추에 돌려 본 것뿐이야.

잡아 둔 이름이 잡아 둔 자리는 아니야. 나간 클래스 이름이 VideoAdapter 라서 이름으로 찾으면 걸리고, 찾은 사람은 예약이 지켜졌다고 결론 내려. 아니라고 말해 주는 건 그 클래스의 첫 줄, 뭘 상속하느냐뿐이야. 예약은 이름이 아니라 코드가 뭘 상속하고 뭘 구현하는지로 점검해.

피파의 고백

같은 seam 에서 세 번째야. 구현이 둘이라고 가르쳤는데 하나였어. 그걸 고치면서 이번엔 seam 을 비디오에 기대 세웠지. '그 예약은 살아 있어.' 비디오는 와서, 잡아 둔 이름을 달고, 그 옆을 지나갔어. 이번엔 docstring 이나 아키텍처 문서부터 안 봤어. 클래스 선언이랑 엔드포인트의 요청 타입부터 읽었어. 이 seam 에 대해 내가 쓴 어느 문장보다 빨리 끝나더라. 교훈은 옷만 바꿔 입고 자꾸 돌아오는데, 늘 같은 거야. 코드를 설명하면서 어긋나지 않는 건 코드 자기 구조뿐이야.

Code

나간 모양 그대로, 필드와 클래스·python
# 필드: 엔드포인트 하나. 잡아 둔 필드가 요청 모양을 골라.
# (줄임)
@router.post("/generate")
async def submit_generate(req: GenerateRequest | VideoRequest, queue: QueueDep, request: Request): ...

class GenerateRequest(BaseModel):     # 이미지 갈래. 부르는 쪽은 그대로
    modality: str = "image"           # 다른 값은 경계에서 돌려보냄

class VideoRequest(BaseModel):        # 2026-09-21 도착, 같은 엔드포인트
    modality: Literal["video"] = "video"
    inputs: VideoInputs               # prompt + 역할 붙은 reference:
                                      #   first, last, reference_image,
                                      #   reference_video, reference_audio
    recipe: VideoRecipe               # width, height, frames, fps, steps, seed

# 클래스: 잡아 둔 이름은 나갔고, 잡아 둔 자리는 안 나갔어.
class Adapter(ABC): ...               # 서브클래스는 여전히 딱 하나
class LocalAdapter(Adapter): ...      # 이미지 경로
class VideoAdapter:                   # Adapter 아님
    def __init__(self, registry, settings, store, local_adapter): ...
class UpscaleAdapter: ...             # 이것도 아님

# 드디어 생긴 router 는 모델 행이 아니라 요청의 종류를 읽어:
adapter = upscale if req.modality == "upscale" else video if req.modality == "video" else local
예약 둘, 결과 둘·text
비디오 몫으로 잡아 둔 자리 둘

  필드 (요청 계약)                     클래스 (Adapter 계층)
  ----------------                     ---------------------
  modality: "image" | "video"          class VideoAdapter(Adapter): ...
  잡아 두는 값: 필드 하나              잡아 두는 값: 첫 구현이 깎아 놓은
                                         서명 하나
  2026-09-21: 비디오가 같은            2026-09-21: 비디오가 자기 클래스로
    엔드포인트로 들어옴. 부르는          옴. local adapter 를 협력자로
    쪽은 아무도 안 바뀜                  쥐고서
  상태: 지어짐                         상태: 우회당함 (세 번째)

세 종류의 일이 같이 쓰는 것 (클래스 아래층)
  큐 하나 * 장치 게이트 하나 * progress 통로 하나
  registry 하나 * 배정 단계 하나 (어느 머신)

'생성은 서로 다른 곳에서 벌어진다' 가 결국 간 곳
  Pro 카탈로그의 열 둘, provider 마다 행 하나:
    execution: local | codex | remote
    billing:   included | subscription | metered

External links

Exercise

네가 아는 시스템에서 앞으로 올 종류의 일을 위해 자리를 잡아 둔 곳을 찾아 봐. 필드든, enum 값이든, 추상 클래스든. 예약마다 그게 어디 사는지 적어. 다른 코드가 부르는 계약(요청, 메시지, 파일 형식) 안이야, 아니면 네 코드만 구현하는 클래스 안이야? 그다음 그 미래의 종류가 지금 것과 다른 수명주기를 들고 온다고 상상해 봐. 어느 예약은 통과하고, 어느 예약은 돌아서 가?
Hint
공개한 계약 안의 예약은 새 모양을 표현할 수 있다는 것만 약속해. 클래스 안의 예약은 새 종류가 옛 종류의 삶을 그대로 살 거라고 약속하고. 깨지는 건 두 번째 약속이야.

Progress

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

댓글 0

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

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