버전 관리와 호환성
버전 관리와 호환성 (Versioning and Compatibility)
더 오래된 상대와 대화하는 법, 무엇이 버려질 수 있는지, 언제 콘텐츠 손실이 경고를 요구하는지 — 1.0 버전을 설명드릴게요.
출처: 문서
본문
한 스트림 위의 두 당사자는 거의 같은 나이가 아닌 경우가 많아요. 이 페이지는 그들이 그렇지 않을 때 서로에게 무엇을 빚지는지 진술합니다.
기본값: 추가는 안전하다 (The default: additions are safe)
프로토콜은 추가하는 방식으로 성장해요 — 새 이벤트 유형, 새 선택 필드, 개방 유니언의 새 멤버. 인식하지 못한 자료가 실행을 중단시키지 않고 살아남으므로, 그런 추가는 모두 오래된 당사자가 만나기에 안전해야 해요(MUST).
따라서 프로듀서는 프로토콜이 이미 기술된 자리를 둔 어떤 것도 오로지 추가 안에만 두어선 안 돼요(MUST NOT). 구체적으로: 실행의 결과(outcome), 메시지의 콘텐츠, 도구 호출의 신원과 결과는 스키마가 그들을 위해 기술한 필드로 이동해야 하며, 오래된 소비자가 버릴 새 이벤트 유형이나 새 속성에만 있어서는 안 돼요.
다운그레이드 (Downgrading)
자신의 상대가 더 오래됨을 아는 당사자는 스트림을 상대가 이해하는 형태로 변환할 수 있어요(MAY). 다운그레이드는 있는 것을 제거하거나 재형성해요; 의미를 지어내선 안 돼요(MUST NOT). 오래된 스키마가 요구하는 필드에 빈 값을 공급하는 것 — 콘텐츠가 이제 부재한 곳에 빈 문자열 — 은 재형성이므로 허용됩니다. 프로듀서가 보낸 적 없는 비어 있지 않은 값을 공급하는 것은 의미를 지어내는 것이므로 금지됩니다.
다운그레이드는 지나가는 길에 잘못된 값을 고쳐선 안 돼요(MUST NOT). 스키마가 거부하는 값을 담은 알려진 필드는 치명적이며, 그것을 받아들일 만한 것으로 바꾸는 심은 그렇지 않으면 보고되었을 결함을 숨깁니다.
다운그레이드는 두 종류로 나뉘며, 그 차이가 누군가에게 알려야 하는지를 결정해요.
무손실(Lossless). 제거된 자료는 오래된 당사자가 행동할 수 있는 것을 아무것도 추가하지 않아요. 서브에이전트 개념이 없는 상대에게 subagentRunId를 벗기는 것은 이런 의미에서 무손실입니다: 이벤트는 여전히 도착하고, 한 스레드로 평탄해질 뿐이에요. 무손실 다운그레이드는 조용할 수 있어요(MAY).
손실(Lossy). 의미를 담는 콘텐츠가 버려지거나 저하돼요. 수명주기 이벤트를 제거하거나, 상대가 표현할 수 없는 콘텐츠 부분을 버리거나, 구조화된 값을 더 낮은 것으로 접는 것은 모두 독자가 보았을 무언가를 잃어요. 손실 다운그레이드는 무엇이 왜 잃었는지 명명하는 경고를 반드시 발행해야 해요(MUST).
경고는 최종 사용자가 아니라 개발자를 위한 것이에요. 버려진 형태를 식별해야 하고(SHOULD), 제거하려면 무엇으로 업그레이드해야 하는지 말해야 해요(SHOULD). 구현은 이러한 경고를 끄는 방법을 제공할 수 있어요(MAY) — 단 기본으로 끄면 안 돼요(MUST NOT).
은퇴한 형태 (Retired shapes)
프로토콜이 한때 기술했고 더 이상 기술하지 않는 형태는 은퇴(retired) 했어요. 은퇴는 삭제가 아니에요: 오래된 프로듀서가 여전히 보내며, 단순히 버리는 소비자는 동작하는 통합을 깨뜨릴 거예요.
- 은퇴한 형태는 무엇이 대체하는지, 변환이 어디에 있는지, 변환 자체가 언제 만료되는지와 함께 폐기 레지스트리(deprecation registry)에 기록되어야 해요(MUST).
- 소비자는 은퇴한 형태를 버리기보다 대체물로 변환해야 해요(SHOULD).
- 그 변환은 미들웨어로 실행되어야 해요(MUST) — 강제(execution enforcement) 전에 — 강제는 은퇴한 형태가 단순히 인식되지 않는 현행 프로토콜에 대해 판단하기 때문이에요.
- 과정에서 콘텐츠를 잃는 변환은 위 규칙에 따라 경고해야 해요(MUST).
은퇴한 형태의 만료가 지나면 구현은 변환을 제거할 수 있어요(MAY), 그 후 그 형태는 다른 어떤 것과 마찬가지로 인식되지 않게 됩니다.
버전 협상 (Version negotiation)
버전은 교환의 각 측을 여는 두 메시지에 in-band로 이동해요. 그래서 기록된 교환은 자기 기술적(self-describing)이며 어떤 transport도 그것을 운반할 필요가 없어요:
- 소비자는
RunAgentInput.protocolVersion에서 자신이 말하는 프로토콜 버전을 선언해요. - 프로듀서는
RUN_STARTED.protocolVersion에서 자신이 말하는 버전을 선언해요 — 입력의 에코가 아니라 자신의 것이에요. 이 쌍이 전체 협상입니다: 각 측은 한 번 스스로를 진술하고, 소비자는 다운그레이드가 일어나는 순간 그것을 봐요. - 두 필드 모두 스키마에서 선택적인데, 부재가 무언가를 뜻하기 때문이에요: 프로토콜이 버전을 운반하기 전의 상대. 이 버전의 구현은 자신의 선언을 보내야 해요(MUST), 단 소비자가 자신의 상대가 필드보다 앞서는 것을 알고, 알려지지 않은 입력 멤버를 옛 파서에 넘겨주지 않기 위해 생략하는 경우는 예외예요.
- 값은 이 스펙이 발행하는 버전 식별자입니다 — 동결된 버전이 살고 있는 세그먼트
MAJOR.MINOR, 구성요소별로 수치 비교됩니다(그래서1.10은1.9보다 최신). 당사자가 해석할 수 없는 선언은 더 새로운 것으로 취급됩니다: 진행하고 경고해야 해요(SHOULD). - 자신이 구현하는 라인의 더 새로운 마이너를 만나는 프로듀서는 실행을 서빙해야 해요(MUST) — 추가는 위 규칙에 따라 안전하므로 — 자신의 버전으로 답하고 경고해야 해요(SHOULD).
RUN_STARTED전에, 자신이 구현하지 않는 메이저 라인의 선언만 거부할 수 있어요(MAY); 자신의 라인의 더 새로운 마이너는 구성상 서빙 가능하며 버전 때문에 거부되어선 안 돼요(MUST NOT). 더 새로운 프로듀서 선언을 만나는 소비자는 처리 모델 아래에서 진행하고 경고해야 해요(SHOULD). - 선언은 실행의 스트림이 말하는 것을 명명하지, 누가 중계하는지를 명명하지 않아요: 실행을 변환 없이 전달하는 프록시는 원래 프로듀서가 말한 것을 선언하고, 변환하는 프록시는 자신이 발행하는 것을 선언해요. 여러 실행을 운반하는 스트림에서 각 실행의
RUN_STARTED는 자신의 실행을 선언하며, 이것이 혼합 연식 역사의 재생이 진실되게 유지되는 방식입니다; 실행을 실시간으로 생성하는 프로듀서는 스트림 중에 말하는 것을 바꾸지 않아요. - transport는 본문을 읽지 못하는 중간자(intermediary)를 위해 선언을 자신의 봉투(예: 미디어 타입 파라미터)에 미러링할 수 있어요(MAY). in-band 필드가 권위가 있으며; 미러링하는 바인딩은 불일치가 어떻게 취급되는지 정의합니다.
당사자가 상대의 버전을 아는 곳 — in-band로 선언되거나 구성으로 — 에서는 그에 기반해 다운그레이드를 선택할 수 있어요(MAY). 모르는 곳에서는 상대가 현행인 것처럼 행동해야 해요(MUST): 아래로 추측하는 것은 이유 없이 스트림을 저하시킬 거예요.
버전으로 선택된 다운그레이드는 스트림의 순수 변환이어야 해요. 실행의 결과를 바꾸거나, 식별자를 변경하거나, 이벤트를 재정렬해선 안 돼요(MUST NOT).
프로듀서의 의무 (What a producer owes)
- 말하는 프로토콜 버전을
RUN_STARTED에 선언. - 현행 프로토콜이 기술하는 형태만 발행.
- 값을 갖지 않는 선택 필드는
null을 보내기보다 생략. - 소비자가 추가를 이해하는 것에 결코 의존하지 않기.
소비자의 의무 (What a consumer owes)
- 필드를 앞서는 상대를 알고 있을 때를 제외하고, run input에서 말하는 프로토콜 버전을 선언.
- 인식하지 못한 이벤트·속성·유니언 멤버를 견디고; 잘못된 알려진 값에서는 실패.
- 강제 전에 미들웨어, 애플리케이션 코드 전에 강제를 실행.
- 변환이 콘텐츠를 잃을 때 경고.
- 강제가 제거했을 자료를 애플리케이션 코드에 제시하지 않기.
더 알아보기 (Learn more)
- 이벤트 모델 — 이벤트 봉투·식별자
- Transports — 스트림 프레이밍과 전달
- 이벤트 패턴 — 이벤트 스트림 구성