接口设计的几个注意事项

本文的“接口”

本文的-"接口",等同于 RPC 类协议/框架中的接口,例如SOAP, Apache Thrift, Apache Avro, Microsoft DCOM, WCF 及集团内最常用的 HSF, Dubbo 等等。此外,还有一些 RESTful 规范/框架(如Jax-RS),在使用体验上也非常类似于 RPC,虽然通常不被归属于RPC, 但本文中的提到某些原则可能同样适用。

一次典型的RPC 请求/响应包含以下几个步骤:

  • 1) 客户端发起一次方法调用
  • 2) 客户端将调用(接口+方法+参数)进行打包
  • 3) 客户端将打包后的内容发送到服务器端
  • 4) 服务器端收到数据并解析为一次方法调用
  • 5) 服务器端在某对象上执行方法(参数)调用
  • 6) 服务器端将得到调用返回值,并对其打包
  • 7) 服务器端将打包后的返回值发送给客户端
  • 8) 客户端收到响应并解析响应数据
  • 9) 客户端得到方法的返回值

由于 2)~8) 的步骤对客户端是透明的,看似乎是本地方法调用,但远程方法调用是不同于本地调用的,使用时也不应该忽视他们存在着不同。
虽然不少框架没有对接口作出更严格的语法限制,但实际使用起来也不能太过任性,适当遵循一些规则习俗,会减少一些不必要的麻烦。以下就过往使用的经验,列举一些较常见的问题。

“接口”的定义

远程接口调用过程,发生了一次数据交换,即使用参数换得一个返回值或异常,一个普通的接口可能是这样的

public interface DemoService {
    public ResultType doAnything(FooType arg1, BarType arg2) throws MyException;
}

不同于本地方法调用的是,远程方法对参数、返回值、异常的定义限制的越“严格”越好-从某种意义上说,参数、返回值、异常的类型都是应该是 Struct而非 Class,那么区别在哪、又为什么这么说呢?

  • 1) Struct 侧重于字段-值,不可被扩展 - 这意味着Client/Server 每一端都不可能在协商好接口定义后单方面对数据进行“画蛇添足”,也不可能发送对方也许会不知道的数据类型,保证双方对收到的数据不存在产生歧义的可能。
  • 2) Class 侧重于功能-方法,通常允许扩展-这意味着 Client/Server 都有可能向对方发送一个对方并不知道的类。比如Client 向 Server 发送了参数 FooType 的扩展类 FooTypeExt, 而 Server 可能因无法解释 FooTypeExt 而产生意外异常。

一些很可能产生歧义的接口定义:

  • 1) 参数类型限定太宽泛,想返回什么都人合乎语法,无法保证 Server 一定会理解该参数
public int saveData(Object data);
  • 2) 返回类型限定太宽泛,想返回什么都人合乎语法,无法保证 Client 一定会理解返回值
public Object saveData(int id);
  • 3) 使用基础/抽象类型,一方可以随意 override 掉 BaseType 的某些行为而使用对方产生某些意外的效果
public int saveData(BaseType data);
public AbstractType getData(int id);
  • 4) 泛型接口,不应当使用
public interface IMyAPI<T extends BaseType> {
    pulic saveData(T data);
    public T getData(int key);
}

这样的接口定义了啥?

  • 5) 泛型方法,不应使用
public <T> T getData(int key);

这样的接口定义了啥?

由于 Java 不支持 Struct,在实际开发中,理论上应该只使用 final 修饰的 POJO 类作为参数/返回值类型,即使参数/返回类型不是密封类,也要避免使用它们的扩展类;
在需要使用 List, Map 等时,虽然语法允许使用它们的任意扩展类,但最好只使用JRE 包含的类而不要随意扩展。

“接口”的发布

“接口” 是Client/Server 数据交换的契约,接口所在包要被 Client/Server 所共享,因此这个包中最好仅是包含接口以及与接口相关的参数类型、返回值类型、异常类型,及其它公用的常量、枚举、资源等等, 而不应该包含Client或Server功能的具体实现
一个常见的现象是接口类被打包在 xxxx-client.jar 中而发布,这种方式存在以下多种问题。

  • 1) server 上仅为了获得接口类,就需要部署 client.jar 及其全部依赖项,导致引用了很多冗余的包。
  • 2) 接口定义经过双方协商后不会轻易变更,但 xxxx-client.jar 因包含有功能实现而会经常更新,继而引起接口使用方的连锁更新,而实际上又是不必要的。
  • 3) 一些接口测试/分析工具,为了获得接口定义,同样也不得不部署整个的 client.jar 及其依赖项。
    另一个常见现在是接口类被打包在 xxxx-common.jar 中发布,如果 common 库中包含有复杂的功能实现,同样也会引发上面两个问题。

小结:接口定义与期所在的包是同命运的,接口不更新则包不应该更新,包若更新则是表示接口已变。因此,在打包和发布接口时,尽量遵循以下

  • a) 把接口类及期附属的参数/返回值/常量/资源等发布在一个 xxxx-api.jar (xxx-service/interface.jar等)包中;
  • b) 不要在这个包中发布Client/Server 上的功能实现类;
  • c) 如果有 Client/Server 共同的功能类,那么把它打包在另一个 xxxx-common.jar(xxxx-shared.jar等) 中;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值