微服务接口定义实践
在进行SOA系统设计,设计微服务的接口形式,会遇到一些问题,
- 入参是多个入参还是包装在一个对象中?
- 异常返回是通过Exception还是返回码?
以下是开发过程中的一些思考与实践:
1、实践
定义成 GetUserInfoResp getUserInfo(GetUserInfoReq req)
的形式,
-
入参
GetUserInfoReq
继承了AbstractReq
,AbstractReq
保存了公用的请求参数成员变量
,如username、ip
等
-
出参
GetUserInfoResp
继承了AbstractResp
,AbstractResp
保存了公用的返回码成员变量
,如retCode
等
代码设计
interface UserProvider {
GetUserInfoResp getUserInfo(GetUserInfoReq req);
}
class GetUserInfoReq extends AbstractReq {
}
class AbstractReq {
private String username;
private String ip;
}
class GetUserInfoResp extends AbstractResp {
private String school;
private double weight;
}
class AbstractResp {
private String retCode;
private String message;
}
2、入参设计
为什么要把各种成员变量包装在一个Req
对象中,而不是做成getUserInfo(String username, String disctrict)
的形式呢?
2.1 方便拓展
- 案例一:
如果这个接口有两个调用方,某一天这个接口新添加了一个入参int gender
,其中一个调用方会传这个参数,另一个调用方不传。
如果是当前这个方案,只需要在Req
对象中添加一个成员变量int gender
即可,然后两个消费方都可以兼容的进行调用。
如果是采用getUserInfo(String username, String disctrict)
的方案,那么就只能新加一个方法getUserInfo(String username, String disctrict, int gender)
了,造成了代码的冗余。
- 案例二:
如果某一天,这个系统要支持不同国家的业务,所有的接口都要添加一个参数String nation
。
那么基于当前的方案,只需要在AbstractReq
中添加一个String nation
即可,非常方便。
如果基于getUserInfo(String username, String disctrict)
的形式,那么可能需要批量修改几百个接口,花费了大量的时间,同时也提高了错误发生的概率。
2.2 方便映射
在SOA的系统设计中,往往会在内部系统的前面添加一个gateway系统来做一些公共逻辑,网关对外提供http服务,而网关和内部系统通过专有协议进行通信,
想想这样一个场景,/user/getUserInfo
这个接口提供获取用户信息的服务,用户的请求body可能有以下几种形式:
- 形式一:
{
"name": "jason",
"nation": "america"
}
- 形式二:
{
"nation": "america",
"name": "jason"
}
要把这些形式的请求映射到后端接口上:
2.2.1 如果是当前最佳实践
即如果是getUserInfo(GetUserInfoReq req)
的形式
那么网关很好设计,
- 提前记录url和接口的映射规则,
- 请求来的时候,把url映射成接口,直接把body反序列化成
GetUserInfoReq
对象即可。
2.2.2 如果是另一种形式
即如果getUserInfo(String name, String nation)
的形式
那么网关很不好设计,
-
提前记录url和接口的映射规则,
这个记录会很麻烦,因为需要记录 body中的每个参数和接口中的第几个参数是对应的
-
映射也很麻烦,
需要遍历body中的变量,把body中的某个变量映射成接口第n个参数
究其原因,是因为通过反射可以获取到一个对象的成员变量的名称,却无法获取到一个方法的参数名称。
2.3 方便封装
封装成Req对象,可以把相关的函数封装在Req对象内部,比如validateParam()
方法。
更进一步,可以定义一个BaseReq
,在其内部定义一个 abstract validateParam()
方法,来强制所有的Req对象定义validateParam()
方法。
public
3、出参设计
为什么通过通过retCode
来包装异常情况,而不是直接抛出异常?
3.1 降低系统间的耦合
如果getUserInfo
通过异常来表达业务异常情况,那调用方势必要接触到 提供方的各种Exception类型,大大提高了系统的耦合性。
3.2 避免延迟 && 避免占用带宽
Exception会带着一个调用栈,
如果通过网络抛出异常,一方面会占用带宽,一方面会造成延迟。
4、总结
基于以上原因,应该采用GetUserInfoResp getUserInfo(GetUserInfoReq req)
的形式定义接口。