接口设计的几个注意事项

简介: # 本文的“接口” 本文的-"接口",等同于 [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) 类协议/框架中的接口,例如SOAP, Apache Thrift, Apache Avro, Microsoft DCOM, WCF 及集团内最常用的 HSF, Dubbo 等等。此外,还有一些 RESTful 规范/框架(如Jax-

本文的“接口”

本文的-"接口",等同于 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等) 中;
目录
相关文章
|
4月前
|
设计模式 Oracle 关系型数据库
二十三种设计模式全面解析-适配器模式的妙用:异构数据库和不同版本API的完美兼容!
二十三种设计模式全面解析-适配器模式的妙用:异构数据库和不同版本API的完美兼容!
|
6月前
|
监控 小程序 Java
《优化接口设计的思路》系列:第五篇—接口发生异常如何统一处理
大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
207 0
《优化接口设计的思路》系列:第五篇—接口发生异常如何统一处理
|
6月前
|
存储 缓存 Java
《优化接口设计的思路》系列:第四篇—接口的权限控制
大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。 作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。
68 0
《优化接口设计的思路》系列:第四篇—接口的权限控制
|
10月前
|
安全
接口开发文档及注意事项
接口开发是指通过定义一组接口,使不同的系统或设备之间能够进行数据通信和互操作的过程。
146 0
|
Java Scala 开发者
封装注意事项|学习笔记
快速学习封装注意事项。
77 0
|
编译器 Scala 开发者
函数使用注意事项和细节2|学习笔记
快速学习函数使用注意事项和细节2。
51 0
|
Java 编译器 Scala
函数使用注意事项和细节3|学习笔记
快速学习函数使用注意事项和细节3。
60 0
|
Java 程序员 Scala
抽象类使用注意事项和细节|学习笔记
快速学习抽象类使用注意事项和细节。
64 0
|
Java 编译器 Scala
函数使用注意事项和细节1|学习笔记
快速学习函数使用注意事项和细节1。
72 0
|
Go 开发者
函数注意事项和细节(1) | 学习笔记
简介:快速学习函数注意事项和细节(1)
67 0