IDL 基础
概述
IDL(Interface Definition Language)是接口定义语言,用于定义服务接口、数据结构和通信协议。CloudWeGo 主要支持 Thrift 和 Protobuf 两种 IDL 格式,其中 Thrift 是推荐的首选格式。
核心内容
Thrift IDL
基本语法
thrift
// 命名空间定义
namespace go example.service
namespace java com.example.service
// 常量定义
const string VERSION = "1.0.0"
const i32 MAX_SIZE = 1024
// 类型别名
typedef i64 UserId
typedef string Username
// 枚举类型
enum Status {
UNKNOWN = 0
ACTIVE = 1
INACTIVE = 2
DELETED = 3
}数据类型
Thrift 支持以下基本数据类型:
| 类型 | 描述 | Go 类型 |
|---|---|---|
| bool | 布尔值 | bool |
| byte | 8位有符号整数 | int8 |
| i16 | 16位有符号整数 | int16 |
| i32 | 32位有符号整数 | int32 |
| i64 | 64位有符号整数 | int64 |
| double | 64位浮点数 | float64 |
| string | UTF-8字符串 | string |
| binary | 二进制数据 | []byte |
结构体
thrift
// 基本结构体
struct User {
1: required i64 id // 必填字段
2: required string name
3: optional string email // 可选字段
4: optional i32 age
5: optional Status status = ACTIVE // 默认值
}
// 嵌套结构体
struct Address {
1: string province
2: string city
3: string street
4: optional string zipCode
}
struct UserProfile {
1: required User user
2: optional Address address
3: optional list<string> tags
4: optional map<string, string> metadata
}容器类型
thrift
struct ContainerExample {
// 列表
1: optional list<i32> numbers
2: optional list<string> names
3: optional list<User> users
// 集合(无序不重复)
4: optional set<i64> ids
5: optional set<string> tags
// 映射
6: optional map<string, i32> scores
7: optional map<i64, User> userMap
8: optional map<string, list<string>> categories
}异常定义
thrift
// 自定义异常
exception UserNotFound {
1: i64 userId
2: string message
}
exception InvalidParameter {
1: string parameter
2: string reason
}
exception ServiceUnavailable {
1: string serviceName
2: i32 retryAfter
}服务定义
thrift
// 基本服务
service UserService {
// 简单请求-响应
User getUser(1: i64 userId) throws (1: UserNotFound ex)
// 返回列表
list<User> listUsers(1: i32 offset, 2: i32 limit)
// 创建用户
i64 createUser(1: User user) throws (1: InvalidParameter ex)
// 更新用户
bool updateUser(1: i64 userId, 2: User user) throws (
1: UserNotFound notFound,
2: InvalidParameter invalid
)
// 删除用户
void deleteUser(1: i64 userId) throws (1: UserNotFound ex)
}流式服务
thrift
service StreamService {
// 服务端流式
Response streamData(1: Request req) (streaming)
// 客户端流式
Response uploadData(1: stream Request req)
// 双向流式
stream Response bidiStream(1: stream Request req)
}Protobuf IDL
基本语法
protobuf
syntax = "proto3";
package example.service;
option go_package = "example/service";消息定义
protobuf
// 基本消息
message User {
int64 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
Status status = 5;
}
// 枚举
enum Status {
UNKNOWN = 0;
ACTIVE = 1;
INACTIVE = 2;
DELETED = 3;
}
// 嵌套消息
message Address {
string province = 1;
string city = 2;
string street = 3;
string zip_code = 4;
}
message UserProfile {
User user = 1;
Address address = 2;
repeated string tags = 3;
map<string, string> metadata = 4;
}服务定义
protobuf
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (User);
rpc UpdateUser(UpdateUserRequest) returns (User);
rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);
}
message GetUserRequest {
int64 user_id = 1;
}
message ListUsersRequest {
int32 offset = 1;
int32 limit = 2;
}
message ListUsersResponse {
repeated User users = 1;
}流式服务
protobuf
service StreamService {
// 服务端流式
rpc StreamData(Request) returns (stream Response);
// 客户端流式
rpc UploadData(stream Request) returns (Response);
// 双向流式
rpc BidiStream(stream Request) returns (stream Response);
}Thrift vs Protobuf
| 特性 | Thrift | Protobuf |
|---|---|---|
| 语法 | 自定义语法 | 自定义语法 |
| 可选字段 | required/optional | optional |
| 默认值 | 支持 | 不支持 |
| 枚举 | 支持 | 支持 |
| 集合类型 | list/set/map | repeated |
| 映射类型 | map | map |
| 流式支持 | streaming 关键字 | stream 关键字 |
| 性能 | 较好 | 更好 |
| 生态 | Kitex 原生 | gRPC 生态 |
最佳实践
1. 字段编号
thrift
// 推荐:预留编号空间
struct User {
1: i64 id
2: string name
// 预留 3-9 用于基本信息
10: optional string email
11: optional string phone
// 预留 11-19 用于联系方式
20: optional Status status
}最佳实践:
- 为不同类型的字段预留编号区间
- 不要重用已删除字段的编号
- 编号从 1 开始,避免使用 0
- 为未来可能的扩展预留空间
2. 版本兼容
thrift
// 使用可选字段保证兼容性
struct UserV2 {
1: required i64 id
2: required string name
3: optional string email // 新增字段使用 optional
4: optional string phone // 新增字段使用 optional
5: optional string avatar // 新增字段使用 optional
}版本兼容策略:
- 新增字段必须使用
optional - 不要修改现有字段的类型
- 不要删除字段,而是标记为 deprecated
- 保持字段编号不变
3. 错误处理
thrift
// 定义统一的错误响应
struct ErrorResponse {
1: i32 code
2: string message
3: optional map<string, string> details
}
// 服务使用统一错误
service UserService {
User getUser(1: i64 userId) throws (
1: ErrorResponse error
)
}错误处理最佳实践:
- 定义统一的错误结构
- 使用错误码进行分类
- 提供详细的错误信息
- 为不同类型的错误定义专门的异常
4. 分页设计
thrift
// 分页请求
struct PageRequest {
1: required i32 page
2: required i32 pageSize
3: optional string sortBy
4: optional bool descending
}
// 分页响应
struct PageResponse {
1: required i32 total
2: required i32 page
3: required i32 pageSize
4: required list<User> items
}分页最佳实践:
- 统一分页请求和响应结构
- 限制 pageSize 的最大值
- 提供排序选项
- 包含总记录数和当前页信息
5. 命名规范
推荐的命名规范:
- 服务名:
PascalCase,如UserService - 结构体名:
PascalCase,如UserProfile - 字段名:
camelCase,如userName - 枚举值:
UPPER_SNAKE_CASE,如ACTIVE_STATUS - 常量名:
UPPER_SNAKE_CASE,如MAX_PAGE_SIZE
6. 注释规范
thrift
// 服务注释
/**
* 用户服务
* 提供用户管理相关功能
*/
service UserService {
/**
* 获取用户信息
* @param userId 用户ID
* @return 用户信息
* @throws UserNotFound 用户不存在
*/
User getUser(1: i64 userId) throws (1: UserNotFound ex)
}
// 结构体注释
/**
* 用户信息
*/
struct User {
/**
* 用户ID
*/
1: required i64 id
/**
* 用户名
*/
2: required string name
}7. 模块化设计
thrift
// common.thrift - 公共定义
namespace go example.common
const string VERSION = "1.0.0"
enum Status {
UNKNOWN = 0
ACTIVE = 1
INACTIVE = 2
}
// user.thrift - 用户相关
namespace go example.service
include "common.thrift"
struct User {
1: required i64 id
2: required string name
3: optional common.Status status
}
service UserService {
User getUser(1: i64 userId)
}版本管理
1. 版本控制策略
语义化版本:
MAJOR.MINOR.PATCHMAJOR:不兼容的API变更MINOR:向后兼容的功能添加PATCH:向后兼容的bug修复
版本管理最佳实践:
- 在IDL文件中定义版本常量
- 使用git标签管理IDL版本
- 维护版本变更日志
- 建立版本发布流程
2. 向后兼容
保持向后兼容的原则:
- 新增字段:必须使用
optional - 删除字段:不要真正删除,而是标记为 deprecated
- 修改字段:不要修改现有字段的类型或含义
- 重命名字段:保留原字段,新增新字段
- 修改服务:新增方法,不要修改现有方法签名
示例:
thrift
// 版本 1.0
struct User {
1: required i64 id
2: required string name
}
// 版本 1.1 - 向后兼容
struct User {
1: required i64 id
2: required string name
3: optional string email // 新增字段
}
// 版本 2.0 - 不向后兼容(需要新的服务名)
service UserServiceV2 {
UserV2 getUser(1: i64 userId)
}
struct UserV2 {
1: required i64 id
2: required string fullName // 重命名字段
3: optional string email
}3. 版本迁移
迁移策略:
- 蓝绿部署:同时运行新旧版本
- 灰度发布:逐步切换流量
- 双写策略:同时写入新旧系统
- 数据迁移:同步数据到新格式
迁移步骤:
- 部署支持新IDL的服务
- 客户端逐步升级
- 监控系统运行状态
- 完全切换到新版本
4. IDL 工具链
推荐工具:
- ThriftGo:CloudWeGo 推荐的 Thrift 编译器
- Protoc:Protobuf 编译器
- IDL Linter:检查IDL语法和规范
- IDL Diff:比较IDL版本差异
工作流:
- 编写IDL文件
- 运行linter检查
- 生成代码
- 编写单元测试
- 提交代码并打标签
跨语言支持
1. 多语言命名空间
thrift
// 多语言命名空间
namespace go example.service
namespace java com.example.service
namespace cpp example.service
namespace py example.service
namespace js example.service2. 语言特定类型
语言特定的类型映射:
| Thrift 类型 | Go | Java | Python |
|---|---|---|---|
| bool | bool | boolean | bool |
| i32 | int32 | int | int |
| i64 | int64 | long | int |
| string | string | String | str |
| binary | []byte | byte[] | bytes |
list<T> | []T | List<T> | list[T] |
map<K,V> | map[K]V | Map<K,V> | dict[K,V] |
3. 跨语言最佳实践
- 使用语言无关的类型
- 避免使用语言特定的特性
- 保持字段名的一致性
- 为不同语言提供适配层
- 编写跨语言测试用例
小结
本章介绍了 CloudWeGo 中使用的 IDL 基础知识:
- Thrift IDL:CloudWeGo 推荐的 IDL 格式,语法简洁,功能丰富
- Protobuf IDL:业界流行的 IDL 格式,性能更好,生态成熟
- 数据类型:基本类型、结构体、容器类型、枚举类型
- 服务定义:请求-响应、流式服务、错误处理
- 最佳实践:
- 字段编号和命名规范
- 版本兼容性设计
- 错误处理和分页设计
- 模块化和注释规范
- 版本管理:
- 语义化版本策略
- 向后兼容性原则
- 版本迁移策略
- IDL 工具链和工作流
- 跨语言支持:
- 多语言命名空间
- 语言特定类型映射
- 跨语言最佳实践
通过本章的学习,你应该掌握了如何设计和管理 IDL 文件,为 CloudWeGo 服务的开发打下坚实的基础。在下一章中,我们将学习如何使用代码生成工具自动生成服务代码,进一步提高开发效率。