跳到主要内容
版本:1.7

Protobuf IDL 支持

本页介绍 Apache Fory 如何处理 Protocol Buffers(.proto)Schema、protobuf 概念如何 映射到 Fory,以及如何使用 protobuf 专属的 Fory 扩展选项。

本页内容​

  • 根据使用场景选择 protobuf 或 Fory
  • 采用过程中需要关注的语法和语义差异
  • protobuf 文件中支持的 Fory 扩展选项
  • 从 protobuf 迁移到 Fory 的实用模式

快速决策指南​

场景推荐格式
正在构建 gRPC API 并依赖 protobuf 工具Protocol Buffers
需要最佳对象图性能和引用跟踪Fory
序列化数据中需要循环引用/共享引用Fory
编码兼容性需要强大的未知字段行为Protocol Buffers
需要原生 struct/类,而不是 protobuf 包装器Fory

Protobuf 与 Fory 对比概览​

方面Protocol BuffersFory
主要用途RPC/消息契约高性能对象序列化
编码模型标签-长度-值Fory 二进制协议
引用跟踪未内置一等功能(ref)
循环引用不支持支持
未知字段保留不保留
生成类型Protobuf 特有模型类型原生语言构造
gRPC 生态原生Java/Python/Go/Rust/C#/Dart/Scala/Kotlin/JavaScript 服务代码生成

Fory 可以使用 --grpc 生成 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 JavaScript gRPC 服务配套代码。JavaScript 浏览器客户端使用 --grpc-web 生成。这些服务使用常规 gRPC 传输,但通过 Fory 而不是 protobuf 序列化请求和响应载荷。对于广泛的 gRPC 生态工具、 Schema 反射和 protobuf 原生 interceptor,protobuf 仍是成熟的默认选择。

为什么使用 Apache Fory​

  • 符合语言习惯的生成代码:Fory IDL 生成可直接用作领域对象的语言原生类和 struct。
  • 更快的序列化:在 Fory 基准测试中,对于对象序列化工作负载,Fory 可比 protobuf 快约 10 倍。
  • 更好的对象图建模:共享引用和循环引用是一等功能,无需应用级 ID 链接变通方案。

基准测试详情请参阅性能参考。

语法和语义映射​

包和文件选项​

Protocol Buffers

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

Fory

package example.models;

Fory 使用一个包命名空间进行跨语言注册。代码生成仍可配置语言特有的包位置。

消息和枚举定义​

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 前缀风格。

oneof 到 union​

Protobuf oneof 会转换为嵌套 Fory 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;
}

说明:

  • 联合 case ID 从原始 oneof 字段编号派生。
  • 合成的联合字段使用最小的 oneof case 编号。

导入和常用类型​

支持 Protobuf import。常见 well-known 类型直接映射:

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

类型映射要点​

Protobuf 类型Fory 映射
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 + 可选联合引用字段
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为此文件中的所有消息生成 Java record
(fory).polymorphismbool默认启用多态序列化元数据
(fory).enable_auto_type_idbool省略时自动生成类型 ID(编译器默认为 true)
(fory).evolvingbool消息的默认 Schema 演进行为
(fory).go_nested_type_stylestringGo 嵌套命名风格:underscore(默认)或 camelcase
(fory).swift_namespace_stylestringSwift 命名空间风格:enum(默认)或 flatten;仅在包非空时适用

消息和枚举选项​

选项适用于类型说明
(fory).idmessage, enumint用于注册的显式类型 ID
(fory).aliasmessage, enumstring用于自动 ID hash 的替代名称
(fory).evolvingmessagebool覆盖文件级演进设置
(fory).use_record_for_javamessagebool为此消息生成 Java record
(fory).deprecatedmessage, enumbool将类型标记为已弃用
(fory).namespacemessagestring覆盖基于包的默认命名空间

字段级选项​

选项类型说明
(fory).refbool为此字段启用引用跟踪
(fory).nullablebool将字段视为可空(optional)
(fory).weak_refbool生成弱指针语义(C++/Rust 代码生成)
(fory).thread_safe_pointerboolRust ref 载体选择;默认 true 使用 Arc/ArcWeak,显式 false 使用 Rc/RcWeak
(fory).deprecatedbool将字段标记为已弃用
(fory).typestring用于 tagged 64 位整数编码的基本类型覆盖

引用选项行为:

  • weak_ref = true 隐含引用跟踪。
  • 对于 repeated 字段,(fory).ref = true 应用于 list 元素。
  • 对于 map<K, V> 字段,(fory).ref = true 应用于 map 值。
  • weak_ref 和 thread_safe_pointer 是 C++/Rust 代码生成提示。
  • thread_safe_pointer 默认为 true;它只改变生成的 Rust 指针载体,不会改变编码格式。
  • 在 Rust 代码生成中,(fory).weak_ref = true 默认使用 ArcWeak,只会切换为 RcWeak;切换条件是设置 (fory).thread_safe_pointer = false。

按形状展示选项示例​

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

引用跟踪与 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 Schema 迁移到 Fory​

第 1 步:转换 Schema 语法​

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

第 2 步:转换 oneof 和特殊类型​

  • oneof -> union + 可选联合字段。
  • 将 protobuf well-known 类型映射到 Fory 基本类型(timestamp、duration、any)。

第 3 步:使用 ref 替换 Protobuf 变通方案​

对于 protobuf 使用手动 ID 链接表示对象图的位置,改用 Fory ref 修饰符(并在需要时 使用可选的 ref(weak=true))。

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

使用目标语言的 Fory 编译器调用替换 protobuf 生成步骤。

对于支持的服务输出,添加 --grpc 以生成 gRPC 配套代码:

foryc api.proto --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc

生成的 Java 服务文件依赖 grpc-java 编译;生成的 Python 服务模块默认使用 grpc.aio; 生成的 Rust 服务文件导入 tonic 和 bytes;生成的 Go 服务文件导入 grpc-go;生成的 JavaScript Node.js 服务文件导入 @grpc/grpc-js;生成的 C# 服务文件导入 Grpc.Core.Api 类型;生成的 Dart 服务文件导入 package:grpc;生成的 Scala 服务文件 依赖 grpc-java 编译;生成的 Kotlin 服务文件依赖 grpc-java 和 grpc-kotlin 编译。请在应用 构建中添加这些依赖项;Fory 软件包不会将 gRPC 添加为硬依赖项。请使用 --grpc-python-mode=sync 生成同步 Python grpcio 配套代码。对 JavaScript 输出使用 --grpc-web 可生成导入 grpc-web 的浏览器客户端。Protobuf oneof 字段会转换为请求和响应消息中的 Fory 联合 字段。直接使用联合作为 RPC 请求或响应类型不属于常规 protobuf RPC 语法。

第 5 步:运行兼容性检查​

对于分阶段迁移,请并行保留两种格式,并通过集成测试验证载荷级一致性。

共存策略​

在分阶段迁移期间,可以并行运行 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。