漂亮的外貌决定了我是否想了解你;漂亮的接口决定了我是否想了解你的代码。 —— 某网友
service设计之『见山是山』
一开始,你觉得接口设计也不过如此,Thrift service 的语法也不过如此,挺简单的嘛,和其他编程语言没有很大的差异。
于是撸起袖子就是干,哪管事后洪水滔天!比如,设计一个『创建用户』的接口,你可能会这样做(这里只是举例,实际开发中的代码设计,确实有很多
考虑过少的情况):
service UserService {
// 初期的需求,只需要调用方提供几个参数就行了
bool CreateUser(1: string name, 2: i64 age, 3: string gender);
}
三下五除二就搞定了,想想还有点小激动。
然而接口一发布出去,就很难更改其签名方式了,这个接口一定会散落到各个调用方代码那里。假设,某一天,由于PM的需求,你不得不将接口
的形式变成:
// 版本2,新添加一些可选参数
bool CreateUser(1: string name, 2: i64 age, 3: string gender, 4: string address= "", 5: string id);
你敢变吗? 很难说。你得和一大帮调用端的兄弟解释,这样的改动是如何如何的迫不得已,是如何如何的必须,总而言之,你得确保调用端升级之后
不能挂掉。另外,别人愿不愿意配合改,还不一定。
要是你偷偷地,没有在告知别人的情况下,将接口签名改掉,然后上线,别人一定会打爆你的头。
service设计之『见山不是山』
经历了上面的痛苦之后,你觉得事情不能这么做,不然没有人敢用你发布的接口了。你觉得很多参数应该使用 struct 包起来,这样才能最大限度
地不破坏接口的签名。于是你决定这样做:
struct UserInfo {
1: required string name;
2: required i64 age;
3: required string gender;
}
bool CreateUser(1: required UserInfo user);
这样,如果后期需要添加字段,那么我在 UserInfo 里面加 optional 字段就好了,这样即便我在不告知别人的情况下上线,也不会影响到别人(不过最好告诉一下别人)。
不过你觉得这样还不过瘾,因为你觉得这样包得还不够彻底,比如这个返回值 bool ,万一哪天需要返回更多的信息呢,我岂不是又陷入『进退两难』
的局面了?
于是你决定这样:
// 1. 请求参数要彻底封装,并且命名上也要做到见名知义
struct CreateUserRequest {
1: required UserInfo user;
}
// 2. 返回参数不允许返回基础类型,一定要包得更加彻底一点
struct CreateUserResponse {
1: required bool isSuccess;
}
// 3. 于是签名就变成了这样
CreateUserResponse CreateUser(1: required CreateUserRequest request);
这样,你才觉得有点满意,因为几乎无论怎么变化,你都只需要给两个 struct 加减 optional 字段而已。你把这个原则推广到你所有的
接口,所有的接口 都是这样的形式:XXXResponse InterfaceName(1: XXXRequest request);
。至此,你才觉得世界才是你想象
的那个完美的世界。
可是,迎接你的也许是吐槽哦,那就是:接口也太啰嗦了吧。调用方在准备参数的时候,不仅要层层包裹,在取响应参数的时候,也会有层层包裹,
真是好生心累。要是结构复杂一点,给人的本能反应就是抗拒反感,并且想逃离。
service设计之『见山还是山』
世界哪有那么完美。
最终你还是需要部分放弃前面所说的完美主义,该设计的地方设计,不该设计的地方就使用原生类型也不错。最后你的接口,既好维护,又
不会啰嗦难用。
- 如果喜欢我的文章,请关注我的公众号:『浮生若梦的编程』。
- 也可以关注我的简书专栏:『浮生若梦的编程』。
- 或者加入我的知识星球,『浮生若梦的编程』,获取更多内容。