Thrift IDL
Thrift 采用IDL(Interface Description Language)
来定义通用的服务接口,并通过生成不同的语言代理实现来达到跨语言、平台的功能,本文对Thrift IDL
文件的常用语法进行说明。
数据类型
基本类型(Base Types)
Thrift 不支持无符号整数类型,因为很多编程语言不存在无符号整数类型。
Type | Desc | GO |
---|---|---|
bool | 布尔值 | bool |
byte | 有符号的 8 位整数 | int8 |
i16 | 有符号的 16 位整数 | int16 |
i32 | 有符号的 32 位整数 | int32 |
i64 | 有符号的 64 位整数 | int64 |
double | 64 位浮点数 | float64 |
string | 文本字符串(UTF-8 编码格式) |
string |
特殊类型(Special Types)
Type | Desc | GO |
---|---|---|
binary | 未编码的字节序列,string 的一种特殊形式 | []byte |
集合容器(Containers)
Type | Desc | GO |
---|---|---|
list<T> |
元素有序列表,允许重复 | []T |
set<T> |
元素无序列表,不允许重复 | []T |
map<K,V> |
key-value 结构数据,key 不允许重复 |
map[K]V |
在使用容器类型时必须指定泛型,否则无法编译 IDL 文件。上述类型中,Go 语言中没有 set 类型,所以set<T>
对应的是切片[]T
。容器中元素可以除了 service 之外的任何类型,包括 exception。
Thrift 文件组成
Document ::= Header* Definition*
Header ::= Include | CppInclude | Namespace
Definition ::= Const | Typedef | Enum | Senum | Struct | Union | Exception | Service
一个 Thrift 文件就是一个 Document,而一个 thrift 文件又是由若干 Header 和 Definition 组成的,当然也可以是一个空文件。
命名空间(namespace)
Thrift 中的命名空间类似 Go 或者 Java 中的package
,提供一种组织(隔离)代码的简便方式。名字空间也可以用于解决类型定义中的名字冲突。由于每种语言均有自己的命名空间定义方式,Thrift 允许开发者针对特定语言定义namespace
。
namespace go base
namespace py base
struct Base {
...
}
// Go 语言转换为以下代码
package base
type Base struct {
...
}
include
在真正的业务开发中,我们不可能把所有的服务都定义到一个文件中,通常会根据业务模块进行拆分,然后将这些服务include
到一个入口文件中。
// base.thrift
namespace go base
struct Base {
...
}
// example.thrift
include "base.thrift" // 使用 include关键字 + Thrift文件路径,引用其它 thrift 文件
struct Example {
// 当引用其它 Thrift 文件中的对象时,使用被引用的 Thrift 文件名作为前缀进行访问
1: base.Base ExampleBase
}
注释(comment)
Thrift 支持 C 多行风格和Java/C++
单行风格,示例如下:
/**
* 多行注释
* 类似 C
*/
// C++/Java 当行注释
常量(const)
const 字段类型 名称标识 = 值 | 列表
// 常量定义
const i32 MALE_INT = 1
const map<i32, string> GENDER_MAP = {1: "male", 2: "female"}
类型别名(typedef)
typedef 原类型 自定义类型
// 某些数据类型比较长可以用别名简化
typedef i32 MyInteger
typedef map<i32, string> gmp
枚举类型(enum)
enum 名称标识 {
字段标识 = int常量
}
// 枚举常量必须是 32 位的正整数,默认从 0 开始赋值
enum GenderEnum{
UNKNOWN // 0
MALE // 1,前一个元素加 1
FEMALE // 2,前一个元素加 1
}
enum Numbers {
ONE = 1 // 1
TWO // 2,前一个元素加 1
THREE // 3,前一个元素加 1
FIVE = 5 // 5
SIX // 6,前一个元素加 1
EIGHT = 8 // 8
}
结构体类型(struct)
struct 是 Thrift 定义的一个通用对象,相当于面向对象语言中的类。格式定义如下:
struct 名称标识 {
数字标识: (required|optional)? 类型 名称标识 (= 值)?
}
// 示例
struct User {
1: required string name // 该字段必填
2: optional i32 age = 0 // 该字段选填
3: bool gender // 默认类型为 required 和 optional 的结合
}
- struct 不能继承,但是可以嵌套,包括引用自己;
- 成员字段是强类型,即明确指定类型,字段不允许重复;
- 成员字段前使用正整数+冒号进行编号(如
1:
),编号不允许重复,可以不连续; - 成员字段间分隔符可以使用
,
和;
,可以混用也可均不用,为方便阅读,建议统一; - 成员字段可以使用 optional 和 required 修饰,也可以不填,使用默认类型。区别在于,optional 修饰的字段只有在被传值时才会被序列化;required 修饰的字段在远程调用时必须传值,也必须被序列化;默认类型字段是 required 和 optional 的结合,可以不传值,但是必须被序列化;
- 成员字段可以设置默认值。
☕️ 示例代码
// guild.thrift
namespace go io.buman.guild
struct Guild {
10: i32 id // 公会 ID
20: string name // 公会名称
}
// player.thrift
include "guild.thrift" // 引用 guild.thrift 文件
namespace go io.buman.player
// 变量默认赋值从 0 开始
enum GenderEnum{
UNKNOWN // 0
MALE // 1
FEMALE // 2
}
// 第一个变量赋值为 1,后面的变量一次递增
enum RoleEnum{
WARIOR = 1 // 战吊 1
MAGE // 法爷 2
WARLOCK // 术士 3
PRIEST // 牧师 4
DRUID // 德鲁伊 5
}
struct Player {
1: i32 id
2: required string name
3: required RoleEnum role
4: required GenderEnum gender = GenderEnum.UNKNOWN
5: Player cp // 可以自己嵌套自己
// 当引用其它 Thrift 文件中的对象时,使用被引用的 Thrift 文件名作为前缀进行访问
6: guild.Guild guild
}
异常(exception)
Thrift 支持在服务里面使用 exception 关键字自定义异常,结构上等同于结构体,便于客户端更好的识别和处理服务的各种异常状况。
exception 名称标识 {
数字标识: (required|optional)? 类型 名称标识 (= 值)?
}
exception PlayerNotFoundException {
1: i32 code = 400,
2: string msg
}
服务(service)
service 名称标识 (extends 名称标识)? {
(oneway)? 返回类型 名称标识(字段列表) throws(字段列表)?
}
service PlayerService {
// 玩家注册
Player signIn(1: Player player)
// 查询所有玩家
list<Player> queryAllPlayer()
// 注册公会
guild.Guild registerGuild(1: guild.Guild guild)
// 查询所有公会
list<guild.Guild> queryAllGuild()
// 为玩家添加cp
Player addCp(1: i32 pid, 2: Player player) throws (1: PlayerNotFoundException e)
// 玩家加入公会
Player joinGuild(1: i32 pid, 2: i32 gid) throws (1: PlayerNotFoundException e)
}
service ExampleService {
oneway void GetName(1: string UserId
void GetAge(1: string UserId) throws (1: Error err)
}
- 命名函数具有参数列表和返回值类型,参数列表可以为空,没有返回值使用 void 关键字修饰;
- 参数列表类似于 struct 中的成员字段定义,每个参数前都需要正整数+冒号编号,编号可以不连续;
- 参数列表分隔符可以使用
,
和;
,可以混用也可均不用,习惯使用,
; - 抛出的异常 exception 也需要被编号;
- 默认情况下,非
oneway
修饰的函数是应答式,即req-resp
,客户端发请求,服务端返回响应;被oneway
修饰后的函数,客户端只是发起请求,无须关注返回,服务端也不会响应,与 void 的区别在于 void 类型的函数还可以返回异常。