我有一个使用Thrift编写的API。例如:
service Api {
void invoke()
}
它有一个功能。我想改变它的行为,让它做另外一些事情,但仍然保留旧版本的行为,以满足期望旧版本行为的客户。
处理新API版本的最佳实践是什么?
我有一个使用Thrift编写的API。例如:
service Api {
void invoke()
}
它有一个功能。我想改变它的行为,让它做另外一些事情,但仍然保留旧版本的行为,以满足期望旧版本行为的客户。
处理新API版本的最佳实践是什么?
Thrift支持软件版本控制,因此可以完全合法地进行服务的第二个版本,其代码如下:
service Api {
void invoke(1: string optional_arg1, 2: i32 optional_arg2) throws (1: MyError e)
i32 number_of_invokes()
}
由于新增的参数在技术上是可选的,任意客户端请求可能包含它们,也可能不包含它们,或者只包含部分参数(例如指定arg1
但不指定arg2
)。异常情况有所不同,旧的客户端将引发某种通用的意外异常或类似的异常。
甚至可以完全删除过时的函数,在这种情况下,旧的客户端在尝试调用(现在不存在的)已删除的函数时会收到异常。
关于向结构、异常等添加成员字段也同样适用于以上所有内容。建议注释掉旧的已删除成员字段和函数,而不是从IDL文件中删除声明,以防止人们在以后的版本中重复使用旧的字段ID、函数名称或枚举值。
struct foobar {
// API 1.0 fields
1: i32 foo
//2: i32 bar - obsolete with API 2.0
// API 2.0 fields
3: i32 baz
}
required
是永久的需要注意的是关键字required
的使用。一旦你发布了一个包含required
成员的结构体API,你将需要一直保留这个成员,直到整个结构体被移除。同样的道理也适用于后续添加新的required
字段。否则,你会面临破坏性变化的风险,因为混合使用旧客户端和新客户端或服务器最终会产生这样的情况:一端绝对期望某个required
成员字段,但是另一端无法提供,仅仅是因为它对此一无所知。
这不是普通或optional
字段的问题,因为Thrift设计为跳过未知字段(类型ID包含在线路数据中),并忽略缺失的字段。相比之下,required
字段应用额外的检查以确保它们存在于线路数据中。
虽然软版本控制是一个很好的工具,但它的代价是由于需要兼容而累积负担。此外,在某些情况下,你的API将会经历破坏性变化,有意地而不是向后兼容。在这种情况下,建议将新服务设置为不同的端点。
或者,可以使用Thrift 0.9.2引入的multiplex协议在同一端点(即套接字、http URI等)上提供多个服务和/或服务版本。
invoke
或类似的方法名称,正是因为这个原因。现在,您的服务将有一个invoke
方法,它执行某些未知操作,还有另一个方法(希望命名更好),它执行其所说的操作。这可能会导致用户混淆,但一切仍将正常工作。