6.1 内置与自定义options:契约里的元数据


6.1 内置与自定义 options:契约里的元数据层

本节摘要:options 机制允许在 proto 文件的任何层级(文件、message、字段、枚举等)挂载元数据:内置 options 如 deprecated、packed、json_name 开箱即用;自定义 options 通过扩展 google.protobuf.*Options 消息把领域元数据刻进描述符,再由插件或反射消费。本节走完自定义 option 的定义、赋值、读取全流程,并给出"什么元数据适合放 option"的设计判据。读完你应当能为团队建一套带语义标注的领域契约。

本章第一站:往罗塞塔石碑上刻新铭文。它承接第 4 章的描述符与插件——没有那两块地基,option 刻了也无人来读。

内置 options:先攒直觉

options 的语法形态是方括号里的键值对,挂在声明的分号之前:

message Artifact { string catalog_no = 1 [deprecated = true]; repeated int32 codes = 2 [packed = true]; // proto3 数值 repeated 默认已 packed string displayName = 3 [json_name = "display_name"]; // 定制 JSON 映射键名 }

三个内置项各代表一类用途:deprecated 是生命周期标注(生成代码产生编译警告,第 5.2 节删字段流程的第一步);packed 是编码行为开关;json_name 是跨格式映射定制。共同点:它们都是"契约本体之外的第二层信息"——不影响字段身份与编码格式,却指导工具与运行时的行为。这个观察就是自定义 option 的设计原点。

自定义 option:三步完整流程

需求场景:团队想在字段上声明数据分级(public / internal / confidential),供文档生成、日志脱敏、CI 检查三个消费方使用。三步走:

第一步:定义 option 消息。 自定义 option 的本质是扩展 Protobuf 自描述体系里的 options 消息——每个可挂载层级都有一个对应的 Options 消息(字段级是 google.protobuf.FieldOptions),扩展它等于往石碑的固定位置加刻文:

syntax = "proto3"; package acme.annotations; import "google/protobuf/descriptor.proto"; message DataClass { // 先定义值的类型 enum Level { LEVEL_UNSPECIFIED = 0; LEVEL_PUBLIC = 1; LEVEL_INTERNAL = 2; LEVEL_CONFIDENTIAL = 3; } } extend google.protobuf.FieldOptions { DataClass data_class = 50001; // 扩展点:编号要用大号(自定义区间) }

第二步:赋值。 在业务契约里像内置 option 一样使用:

message Customer { string nickname = 1 [(acme.annotations.data_class).level = LEVEL_PUBLIC]; string phone = 2 [(acme.annotations.data_class).level = LEVEL_CONFIDENTIAL]; }

第三步:读取。 两条消费路径。编译期路径:第 4.3 节的文档插件里,f.GetOptions() 拿到字段 options 后,按扩展编号 50001 解出 DataClass——文档里 phone 字段旁边就能印上"机密"标记。运行时路径:反射 API 同样能读到(下一节展开)。两条路径共享同一份描述符数据,一次刻写、处处可读。

⚠️ 扩展编号的纪律:自定义 option 的编号要在描述符预留的大号区间(各语言实现普遍建议 50000 以上),且全工程唯一。两个团队都用 50001 定义不同的 option,合并描述符时就是编号重用事故的元数据版本。

设计判据:什么该进 option

option 很容易滥用,三条判据收口:

  1. 跨多个消费方的领域知识才值得刻:日志脱敏、文档标记、CI 校验至少两个消费方,单一用途的信息写在注释里更便宜;
  2. 结构化、可枚举的才刻:option 的价值在于机器可读,自由文本塞进去等于把注释搬了个位置;
  3. 稳定的才刻:option 一旦被工具消费,它的语义变更就是兼容性问题——比第 5 章字段语义更隐蔽,因为连序列化字节都不会变,只有工具行为悄悄漂移。

反例:把"这个字段每秒大约多少次更新"这类运营数据刻进 option——它是环境事实不是契约事实,属于监控系统的领地。

图 6-2 自定义 option 的写入与消费全景

图 6-2 自定义 option 的写入与消费全景

实战案例:日志脱敏系统的契约驱动改造

背景:某系统的日志脱敏靠一张人工维护的"敏感字段对照表"(字段名→脱敏规则),三个月里两次漏更新——新字段上线,脱敏表没人改,用户手机号进了日志被安全团队通报。操作:引入自定义 option log_mask(枚举值:明文、部分遮蔽、完全丢弃),定义进公共 annotations 包;全部消息的敏感字段补标注(phone 标部分遮蔽、token 标完全丢弃);日志库改造——序列化消息进日志前用反射遍历字段(下一节的循环),读到 mask 标注就应用对应规则;CI 加一条检查:新增 string 字段若未标注 log_mask,构建警告。结果:脱敏规则与契约同源演进,新字段从上线第一天起行为正确;对照表删除,漏更新类事故结构性消失。解读:这个案例是 option 机制的教科书应用——领域知识(敏感性)刻进契约、消费方(日志库、CI)从描述符读取、单一事实源消灭了人工同步。变式:同一套标注还能喂给第 4 章的文档插件(文档里印敏感标记)与数据团队(数仓入库时按标注决定是否落地原值)——消费方越多,刻写的复利越高。

本节要点回顾

  • option 是第二层信息:不影响字段身份与编码,指导工具与运行时行为;
  • 自定义三步:扩展 google.protobuf 对应 Options 消息定义(大号编号)→ 契约里方括号赋值 → 插件或反射读取;
  • 编号纪律:自定义区间(50000 以上)且全工程唯一,元数据版编号重用同样致命;
  • 三条判据:多消费方、结构化、稳定——不满足就写注释;
  • 复利模式:一次刻写、文档/日志/CI/数仓多处消费,单一事实源消灭人工同步。

铭文刻好了,下一节学怎么读整块石碑:运行时反射让代码在不认识具体类型的情况下遍历、读取、修改任意消息。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U