콘텐츠로 이동

튜토리얼: 통합 Write — 오버라이드와 필드 선택

소요: 약 10분 · 선행: 빠른 시작 또는 C# 가이드

English: Use the language switcher (top right).


언제 쓰나

큰 메시지 인스턴스 하나많은 수신자에게 보내야 하고, 수신자마다 소수의 필드만 다를 때(표시 이름, 읽음 여부, 로케일별 문구 등). 전체를 N번 Clone하면 비용이 큽니다.

득팩은 C# 생성 struct마다 Pack(format, fieldIds, overrides?)`** 를 추가합니다. **`fieldIds`에 `null이면 전체 필드를 보내고, 선택적으로 Dictionary<int, object>로 필드 ID별 치환값을 넘기며, 메시지 객체를 바꾸거나 통째로 복제하지 않습니다.


1. IDL의 필드 ID

Thrift / 득팩 struct는 숫자 필드 ID를 씁니다.

struct ChatMessage {
    > 1 msgId;
    > 2 body;
    > 3 displayNameForRecipient;
    > 4 isReadForRecipient;
}

npx deukpack ... --csharp 후 생성된 클래스에는 nested FieldId 클래스가 자동 포함됩니다. 숫자를 직접 쓸 필요 없이 ChatMessage.FieldId.DisplayNameForRecipient처럼 이름으로 참조합니다. IDL이 바뀌면 재생성 시 컴파일 타임에 깨집니다.


2. C# — 오버라이드(전체 필드)

var msg = BuildMessageOnce(); // 무거운 작업은 한 번

var overrides = new Dictionary<int, object>
{
    { ChatMessage.FieldId.DisplayNameForRecipient, "Alice (you)" },
    { ChatMessage.FieldId.IsReadForRecipient, false }
};
byte[] bin = msg.Pack(DpFormat.Binary, null, overrides);
network.Send(bin);

규칙:

  • = StructName.FieldId.PropertyName (생성된 const int). 숫자 직접 사용 금지.
  • = 생성된 프로퍼티와 같은 CLR 타입 (타입이 맞지 않으면 캐스트 예외).
  • overridesnull이거나 비어 있으면일반 ``Pack() 과 동일하게 전체가 쓰입니다.
  • 필드가 다른 struct이거나 list/map이면 값은 그 필드 타입 전체 인스턴스(또는 컬렉션 전체)로 넘긴다. 안쪽 멤버만 골라 바꾸는 중첩 키 API는 없다. → 코어 [DEUKPACK_WRITE_WITH_OVERRIDES_API.md](https://github.com/joygram/DeukPack/blob/main/docs/DEUKPACK_WRITE_WITH_OVERRIDES_API.md)§1.3.

전체 API 표·C++/JS: API·타입 참조 · 코어 저장소 DEUKPACK_WRITE_WITH_OVERRIDES_API.md.


3. JavaScript (--js)

js/generated_deuk.js struct 헬퍼마다 pack`**, **`unpack`**, **`FieldId가 붙는다 (v1.7.6+ 표준; toBinary/toJson은 deprecated alias):

var F = ChatMessage.FieldId;
// JSON으로 pack (오버라이드 포함)
var json = ChatMessage.pack(msg, 'json', null, {
  [F.DisplayNameForRecipient]: "Bob",
  [F.IsReadForRecipient]: true
});
// 바이너리로 pack
var bin = ChatMessage.pack(msg, undefined, null, {
  [F.DisplayNameForRecipient]: "Bob"
});
var F = ChatMessage.FieldId;
var json = ChatMessage.toJson(msg, null, {
  [F.DisplayNameForRecipient]: "Bob",
  [F.IsReadForRecipient]: true
});

4. C++

각 struct에 C#/JS와 맞는 `kFieldId_* 상수가 있습니다. 타입 옆에 생성된 pack/바이너리(또는 Thrift 호환) 쓰기 경로를 쓰면 되며, 현재 생성물에는 별도 **Pack`** 단계가 없습니다.

API·타입 참조 참고.


5. 필드 선택

`Pack(format, fieldIds, overrides?) 에서 fieldIds가 null이 아니면 `나열된 필드만 직렬화합니다. partial 타입이나 필드 단위 복사가 필요 없습니다.

var full = LoadFullUser();
byte[] bin = full.Pack(DpFormat.Binary, new[] {
    UserRecord.FieldId.DisplayName,
    UserRecord.FieldId.Level,
    UserRecord.FieldId.AvatarUrl
});

선택과 오버라이드를 함께:

byte[] bin2 = full.Pack(DpFormat.Binary, new[] {
    UserRecord.FieldId.DisplayName,
    UserRecord.FieldId.Level
}, new Dictionary<int, object> {
    { UserRecord.FieldId.DisplayName, r.LocalizedName }
});

JavaScript — 인자 형태 동일:

var F = UserRecord.FieldId;
var ids = [F.DisplayName, F.Level];
var json = UserRecord.pack(full, 'json', ids, null);
var json2 = UserRecord.pack(full, 'json', ids, { [F.DisplayName]: r.LocalizedName });

6. struct extends — 상속

IDL에서 extends를 쓰면 부모 필드를 자식에 자동 병합한다. 공통 필드를 한 곳에 정의하고, 자식은 고유 필드만 추가하면 된다.

struct UserBase {
    > 1 id;
    > 2 displayName;
}

struct UserFull extends UserBase {
    > 3 level;
    > 4 avatarUrl;
}

생성된 UserFull은 필드 4개(1~4)를 모두 갖는다. 다단 상속(A → B → C)도 지원하며 필드 ID 충돌 시 빌드 에러가 난다.


7. 관련: 와이어 프로파일(서브셋 타입)

다른 목적: 클라이언트 프로파일마다 필드 자체를 숨김wireProfiles annotation과 --wire-profile. DEUKPACK_WIRE_PROFILE_SUBSET.md.


8. 기능 비교표

오버라이드(전체 필드) 필드 서브셋 Wire Profile extends
목적 전 필드 전송, 일부 값만 교체 선택한 필드 전송 빌드 타임 서브셋 타입 공통 필드 상속
결정 시점 런타임 런타임 빌드 타임 IDL 정의 시
API Write(oprot, null, overrides) Write(oprot, fieldIds, overrides?) 모든 기능과
동적 선택 가능 가능 불가

9. 코어 저장소 샘플


다음 단계