Skip to content

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
byte8位有符号整数int8
i1616位有符号整数int16
i3232位有符号整数int32
i6464位有符号整数int64
double64位浮点数float64
stringUTF-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

特性ThriftProtobuf
语法自定义语法自定义语法
可选字段required/optionaloptional
默认值支持不支持
枚举支持支持
集合类型list/set/maprepeated
映射类型mapmap
流式支持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.PATCH
  • MAJOR:不兼容的API变更
  • MINOR:向后兼容的功能添加
  • PATCH:向后兼容的bug修复

版本管理最佳实践

  • 在IDL文件中定义版本常量
  • 使用git标签管理IDL版本
  • 维护版本变更日志
  • 建立版本发布流程

2. 向后兼容

保持向后兼容的原则

  1. 新增字段:必须使用 optional
  2. 删除字段:不要真正删除,而是标记为 deprecated
  3. 修改字段:不要修改现有字段的类型或含义
  4. 重命名字段:保留原字段,新增新字段
  5. 修改服务:新增方法,不要修改现有方法签名

示例

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. 版本迁移

迁移策略

  • 蓝绿部署:同时运行新旧版本
  • 灰度发布:逐步切换流量
  • 双写策略:同时写入新旧系统
  • 数据迁移:同步数据到新格式

迁移步骤

  1. 部署支持新IDL的服务
  2. 客户端逐步升级
  3. 监控系统运行状态
  4. 完全切换到新版本

4. IDL 工具链

推荐工具

  • ThriftGo:CloudWeGo 推荐的 Thrift 编译器
  • Protoc:Protobuf 编译器
  • IDL Linter:检查IDL语法和规范
  • IDL Diff:比较IDL版本差异

工作流

  1. 编写IDL文件
  2. 运行linter检查
  3. 生成代码
  4. 编写单元测试
  5. 提交代码并打标签

跨语言支持

1. 多语言命名空间

thrift
// 多语言命名空间
namespace go example.service
namespace java com.example.service
namespace cpp example.service
namespace py example.service
namespace js example.service

2. 语言特定类型

语言特定的类型映射

Thrift 类型GoJavaPython
boolboolbooleanbool
i32int32intint
i64int64longint
stringstringStringstr
binary[]bytebyte[]bytes
list<T>[]TList<T>list[T]
map<K,V>map[K]VMap<K,V>dict[K,V]

3. 跨语言最佳实践

  • 使用语言无关的类型
  • 避免使用语言特定的特性
  • 保持字段名的一致性
  • 为不同语言提供适配层
  • 编写跨语言测试用例

小结

本章介绍了 CloudWeGo 中使用的 IDL 基础知识:

  1. Thrift IDL:CloudWeGo 推荐的 IDL 格式,语法简洁,功能丰富
  2. Protobuf IDL:业界流行的 IDL 格式,性能更好,生态成熟
  3. 数据类型:基本类型、结构体、容器类型、枚举类型
  4. 服务定义:请求-响应、流式服务、错误处理
  5. 最佳实践
    • 字段编号和命名规范
    • 版本兼容性设计
    • 错误处理和分页设计
    • 模块化和注释规范
  6. 版本管理
    • 语义化版本策略
    • 向后兼容性原则
    • 版本迁移策略
    • IDL 工具链和工作流
  7. 跨语言支持
    • 多语言命名空间
    • 语言特定类型映射
    • 跨语言最佳实践

通过本章的学习,你应该掌握了如何设计和管理 IDL 文件,为 CloudWeGo 服务的开发打下坚实的基础。在下一章中,我们将学习如何使用代码生成工具自动生成服务代码,进一步提高开发效率。