跳到主要内容
版本:0.15

Protobuf IDL 支持

本页说明 Apache Fory 如何处理 Protocol Buffers(.proto)schema、protobuf 概念如何映射到 Fory,以及 protobuf 专用 Fory 扩展选项的使用方式。

本页内容

  • 如何在具体场景下选择 protobuf 或 Fory
  • 迁移时需要关注的语法与语义差异
  • .proto 文件中支持的 Fory 扩展选项
  • 从 protobuf 迁移到 Fory 的实践路径

快速决策指南

场景建议格式
主要构建 gRPC API,依赖 protobuf 工具链Protocol Buffers
需要极致对象图性能与引用跟踪Fory
需要在序列化数据中表达循环/共享引用Fory
需要强 unknown-field 语义保证线格式兼容Protocol Buffers
希望直接使用原生 struct/class,而非 protobuf 包装类型Fory

Protobuf 与 Fory 对比

维度Protocol BuffersFory
主要目标RPC/消息契约高性能对象序列化
编码模型Tag-Length-ValueFory 二进制协议
引用跟踪非内建一等支持(ref
循环引用不支持支持
未知字段保留不保留
生成类型protobuf 专用模型类型语言原生构造
gRPC 生态原生成熟持续建设中(活跃开发)

Fory 的 gRPC 支持仍在持续开发中。当前生产级 gRPC 工作流里,protobuf 仍是更成熟的默认选择。

为什么使用 Apache Fory

  • 代码更贴近语言习惯:Fory IDL 生成的类和结构体可直接作为业务领域对象使用。
  • 序列化性能更高:在 Fory 基准中,Fory 在对象序列化场景中可显著快于 protobuf。
  • 对象图表达更自然:共享引用和循环引用是内建能力,无需通过业务层 ID 关联绕过。

性能细节请参见 性能参考

语法与语义映射

Package 与文件级选项

Protocol Buffers

syntax = "proto3";
package example.models;
option java_package = "com.example.models";
option go_package = "example.com/models";

Fory

package example.models;

Fory 使用统一 package 命名空间做跨语言注册。语言特定的包路径仍可在代码生成阶段单独配置。

Message 与 Enum 定义

Protocol Buffers

message User {
string id = 1;
string name = 2;
optional string email = 3;
int32 age = 4;
repeated string tags = 5;
map<string, string> metadata = 6;
}

enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_ACTIVE = 1;
}

Fory

message User [id=101] {
string id = 1;
string name = 2;
optional string email = 3;
int32 age = 4;
list<string> tags = 5;
map<string, string> metadata = 6;
}

enum Status [id=102] {
UNKNOWN = 0;
ACTIVE = 1;
}

关键差异:

  • Fory 可直接分配稳定类型 ID([id=...])。
  • Fory 使用 list<T>repeated T 为兼容别名)。
  • 枚举命名更偏向语言习惯,而非 protobuf 前缀风格。

oneofunion

protobuf 的 oneof 会被翻译为嵌套 Fory union,并增加一个可选字段指向该 union。

Protocol Buffers

message Event {
oneof payload {
string text = 1;
int32 number = 2;
}
}

转换后的 Fory 结构

message Event {
union payload {
string text = 1;
int32 number = 2;
}
optional payload payload = 1;
}

说明:

  • union case ID 来自原 oneof 字段号。
  • 自动生成的 union 引用字段使用 oneof 中最小字段号。

Import 与 Well-Known Types

支持 protobuf import。常见 well-known types 会直接映射:

  • google.protobuf.Timestamp -> timestamp
  • google.protobuf.Duration -> duration
  • google.protobuf.Any -> any

类型映射要点

Protobuf TypeFory 映射
boolbool
int32, uint32可变长 32 位整型家族
sint32zigzag 32 位整型
int64, uint64可变长 64 位整型家族
sint64zigzag 64 位整型
fixed32, fixed64定长无符号整型家族
sfixed32, sfixed64定长有符号整型家族
float, doublefloat32, float64
string, bytesstring, bytes
repeated Tlist<T>
map<K, V>map<K, V>
optional Toptional T
oneofunion + 可选 union 引用字段
int64 [(fory).type = "tagged_int64"]tagged_int64 编码
uint64 [(fory).type = "tagged_uint64"]tagged_uint64 编码

Fory 扩展选项(Protobuf)

.proto 文件中的 Fory 专用选项使用 (fory). 前缀。

option (fory).enable_auto_type_id = true;

message TreeNode {
TreeNode parent = 1 [(fory).weak_ref = true];
repeated TreeNode children = 2 [(fory).ref = true];
}

文件级选项

选项类型说明
(fory).use_record_for_java_messagebool对该文件所有 message 生成 Java record
(fory).polymorphismbool默认开启多态序列化元信息
(fory).enable_auto_type_idbool缺失时自动生成类型 ID(编译器默认 true)
(fory).evolvingboolmessage 的默认 schema 演进行为
(fory).go_nested_type_stylestringGo 嵌套命名风格:underscore(默认)或 camelcase

Message 与 Enum 级选项

选项作用对象类型说明
(fory).idmessage, enumint显式类型 ID(用于注册)
(fory).aliasmessage, enumstring自动 ID 哈希使用的别名
(fory).evolvingmessagebool覆盖文件级演进设置
(fory).use_record_for_javamessagebool为该 message 生成 Java record
(fory).deprecatedmessage, enumbool标记类型为弃用
(fory).namespacemessagestring覆盖默认 package 命名空间

字段级选项

选项类型说明
(fory).refbool为该字段启用引用跟踪
(fory).nullablebool将字段视为可空(optional
(fory).weak_refbool生成弱指针语义(C++/Rust 代码生成)
(fory).thread_safe_pointerboolref 字段在 Rust 中的指针类型(Arc vs Rc
(fory).deprecatedbool标记字段为弃用
(fory).typestring基础类型覆盖,目前支持 tagged_int64/tagged_uint64

引用相关行为:

  • weak_ref = true 隐含开启 ref 跟踪。
  • repeated 字段,(fory).ref = true 作用于列表元素。
  • map<K, V> 字段,(fory).ref = true 作用于 map value。
  • weak_refthread_safe_pointer 是 C++/Rust 代码生成提示。

典型选项组合示例

message Graph {
Node root = 1 [(fory).ref = true, (fory).thread_safe_pointer = false];
repeated Node nodes = 2 [(fory).ref = true];
map<string, Node> cache = 3 [(fory).ref = true];
Node parent = 4 [(fory).weak_ref = true];
}

引用跟踪 vs Protobuf ID 关联

protobuf 本身不保留共享/循环对象图。借助 Fory protobuf 扩展,可以显式启用对象图语义。

不使用 Fory ref 选项(protobuf 风格 ID 关联):

message TreeNode {
string id = 1;
string parent_id = 2;
repeated string child_ids = 3;
}

使用 Fory ref 选项(对象图语义):

message TreeNode {
TreeNode parent = 1 [(fory).weak_ref = true];
repeated TreeNode children = 2 [(fory).ref = true];
}

迁移指南:Protobuf 到 Fory

第 1 步:转换 schema 语法

  • 保持 package 名称稳定。
  • repeated T 改为 list<T>(或保留 repeated 别名)。
  • 在需要稳定数值注册的位置添加显式 [id=...]

第 2 步:处理 oneof 与特殊类型

  • oneof -> union + 可选 union 字段。
  • 将 protobuf well-known types 映射到 Fory 基础类型(timestampdurationany)。

第 3 步:用 ref 替换 protobuf 的对象图绕过方案

若 protobuf 里通过手工 ID 关联表达对象图,迁移到 Fory 后应改用 ref 修饰符(必要时使用 ref(weak=true))。

第 4 步:更新构建与代码生成

将 protobuf 代码生成步骤替换为 Fory 编译器针对目标语言的生成命令。

第 5 步:执行兼容性验证

分阶段迁移时,可并行保留两种格式,并通过集成测试验证 payload 级一致性。

共存策略

迁移期间可以并行运行 protobuf 与 Fory:

public byte[] serialize(Object obj, Format format) {
if (format == Format.PROTOBUF) {
return ((MessageLite) obj).toByteArray();
}
return fory.serialize(obj);
}

可在服务边界设置转换层,并优先迁移内部对象图较重的链路。

性能参考

总结

当主要关注 API 契约与 gRPC 生态时,建议使用 protobuf。若主要关注对象图性能、原生数据模型与引用语义,建议使用 Fory。