先把 Joshua Bloch 大神的 API PDF 放在这里膜拜下:“How to Design a Good API and Why it Matters.pdf”

总述###

在设计和实现通用订单搜索API的过程中,收获了一点关于API设计的得与失。总结下,希望能给后面的工作带来有益的帮助。

什么是好的API ?

简洁、清晰、易懂、易使用。 语义行为与选项分离。

  • Easy to learn
  • Easy to use, even without documentation
  • Hard to misuse
  • Easy to read and maintain code that uses it
  • Sufficiently powerful to satisfy requirements
  • Easy to extend
  • Appropriate to audience

得:

从交易订单搜索能力化角度思考API,充分考虑了组合性,做到足够灵活,同时兼顾可读性。能够在较少改动和发布 trade-manage 的基础上,实现多样化的需求。

失:

在过于注重灵活性功能的情况下,有一些地方做的不够好,给使用方带来很多困扰。

本文主要讨论其得与失,希望能够给后来者带来有益的启发。

好的地方###

能力化####

强调从能力的角度而不是业务场景角度来思考API设计,考虑足够灵活性。 比如

  1. 定义交易订单搜索能力需要的参数,而不是依赖页面传过来的参数。通过定义的参数来组合搜索能力。

  2. 虽然搜索页面只需要传一个订单类型,但是后台允许传多个订单类型,为后续扩展性留下空间。

实现清晰####

基于自身定义的订单搜索参数,后台实现也非常简洁,而不需要做各种参数的解析和适配。对于搜索能力的稳定性和减少BUG的发生,是非常有益的。

一体两面,总有正面和反面的地方。由于通用订单搜索API 特别强调基础层的能力化和稳定性,在基础层与业务层之间缺乏一道友好的适配层,给业务方使用也带来了不少曲折。

详细的文档####

其实通用订单搜索API 的文档也做了不少工作。 接口入参 ,代码示例, 搜索入参的分类,包括代码里 java doc 其实也写得很清楚。 为什么业务方还来咨询呢? 一个原因,确实 API 设计有失当之处(考虑了最主要的PC列表页面搜索场景),对于业务方的简单需求(比如按照下单人ID)显得过于复杂了; 另一个原因,我认为开发人员对于陌生事物还是缺乏必要的耐心。当然我其实也有这个毛病。 如果说文档没有,代码里 java doc API 也缺失,过来咨询是没问题的,但是文档 ,javadoc 注释写的很明白了,花2分钟稍微扫一下,能够学到更多东西,就不会为了一个小需求来咨询了。

但是文档确实也有写的不够的地方。

比如返回的错误信息,文档没有写明。于是联调的时候,业务方遇到“非法的调用源”,一脸懵逼。需要有一些错误提示文档 ; 比如业务方遇到其他的错误,也不知道是什么原因,只能找我们来查。

比如没有突出常用的需要的东西。往往业务方内部依赖订单搜索列表的时候,基于应用场景,可能更多依赖于 订单状态,订单类型,粉丝ID,下单人ID,维权状态, 这些可以单独抽离一个更 brief 的订单搜索接口,并辅以文档,也许就不会遭遇不知道用哪个参数的问题了。 换句话说, 原来过于依赖能力化的角度思考接口,缺失的一块是, 基于应用场景的角度来思考接口。

不好的地方###

盲增参数####

先说下背景。当时是因为老搜索接口不太灵活,且参数与前端页面耦合比较紧,因此决定设计一个新的通用订单搜索接口,以提供更加灵活的搜索能力。

第一个失,就是不加思索地把老搜索接口里的参数全部拷贝过来。 比如说 毫无卵用的 订单标记,被废弃还让业务方传错的 pageSize, pageNo 。

经验: 由于新接口上线和接入通常有一个过程,其实不必要立即支持所有可能的搜索请求,而是先支持最常用的部分,然后根据情况扩展能力。 API 参数通常是越严格越少更好,公开了就收不回来了。

  • 设计新接口入参时,可以参考老接口入参,但一定要慎之又慎,只取必需的,严格把控每个参数的必要性。

  • 参数一定要仅仅针对接口服务而言,不要为了图方便把所有入参都混杂到一个类里,导致API语义不清,给业务方使用带来困惑。

继承滥用####

第二个失,过于追求复用代码和语义分离,继承层次比较深。

追求代码复用是好习惯,但不能过度。尤其 API 设计,应该以“清晰易使用”为重。 如下是个反例:

经验: 继承一般是为了实现不同的语义分离,比如基础通用参数与业务参数, 但强烈建议最多两层,继承层次绝不要超过两层。


public class PaginationParam extends BaseParam { } public class GeneralOrderSearchParam extends PaginationParam { protected String index = "trade_es_index"; /**
* 订单查询对象
*/
protected OrderSearchParam orderSearchParam; // ...
} public class OrderSearchParam extends BaseParam { }

使用体验不佳####

第三个失,没有充分考虑业务方的使用体验。

层次感不强

所有的搜索参数都平铺到一个 OrderSearchParam 的字段里,没有层次感, 业务方往往只要根据一个字段搜索,却要在繁多的搜索字段里搜索。

构造入参不方便

缺乏方便的构造搜索入参的方法,业务方需要写很无聊的 set , set , set 来构造搜索入参,代码会比较难看。 可以考虑使用 Builder 模式来构造常用参数。这个方法也可以解决 层次感不强 的问题。

经验: 应该对搜索字段进行区分对待,重点突出, 可以将最常用的搜索字段聚合成一个子搜索对象,有关联语义的搜索字段聚合成一个子搜索对象。 让业务方能够快速找到所需要的。通过提供易懂的 API 示例,也可以帮助业务方更好地使用 API。

常用搜索重复构建

由于设计参数时,参数取值过于原子化,强调灵活性与可组合性,需要业务方根据搜索场景自行组装参数,当多个业务方来对接某个常用搜索场景时,每个业务方都需要写相同的代码。

if (someCond) {
setOrderTypeDesc(Arrays.asList(xEnum.name(),yEnum.name(),zEnum.name()))
}

如果后续设计有变更,每个业务方都必须修改一遍。因此,更好的做法是,若组合语义占了常用80%的情形,那么应该清晰定义这些组合语义,内部做映射消化掉,而不是委托给外部。

参数混杂####

为了图方便,直接在原来的参数里加字段,而不是创造新的参数对象,导致原有的参数对象混杂不同的功能,给业务方使用带来困惑。

比如原来有个实时详情接口的入参 OrderQueryOptions , 非实时详情接口,多了个 withDeliveryInfo 可选参数,虽然放在原来的参数对象会省不少事,可是会让接口语义不清。对应实时接口来说,它根本不需要这个参数;对于非实时接口,又混杂了实时接口的入参。 两边都不讨好。

经验: 严格对接口入参进行控制,与接口服务无关的参数不允许加入。实现细节相关的东西不加入API,不公开。

暴漏内部细节的扩展方式####

为了具有更好的可扩展能力,减少发布,设计了一个入参 extendKeywords 。当要搜索的字段不在给定搜索入参时,可以直接指定 ES 里对应的字段及操作符来搜索。 虽然灵活了些,却将业务方与底层实现紧紧绑在一起。如果以后要改用其他的搜索引擎,这种就是非常难以摆脱的困扰了。对于API设计与实现来说,都是不好的决策。

小结###

API 是服务提供的接口抽象。 一旦公开,收回来会非常困难而且费力。因此,设计API 要精思细虑,严格把关每个入参字段、继承层次不要超过两次、充分考虑使用者体验、避免参数混杂、避免暴漏实现细节等问题。

API 好书推荐:《软件框架设计的艺术》,英文书名是:《 Practical API Design: Confessions of a Java Framework Architect 》

通用订单搜索的API设计得失录的更多相关文章

  1. Web API 设计摘要

    近期读了一本微电子书 Brian Mulloy 所著<Web API Design>感觉颇多收获,特对其内容做了个整理摘要以便回想其观点精华以指导日常工作中的设计思路. 本文主要讲述 We ...

  2. API设计原则

    译序 Qt的设计水准在业界很有口碑,一致.易于掌握和强大的API是Qt最著名的优点之一.此文既是Qt官网上的API设计指导准则,也是Qt在API设计上的实践总结.虽然Qt用的是C++,但其中设计原则和 ...

  3. (转)RESTful API 设计最佳实践

    原文:http://www.oschina.net/translate/best-practices-for-a-pragmatic-restful-api 数据模型已经稳定,接下来你可能需要为web ...

  4. 一篇文章帮你梳理清楚API设计时需要考虑的几个关键点

    本文作者是Enchant的架构师,他最近研究了Netflix.SoundCloud.谷歌.亚马逊.Spotify等公司的微服务实践,并根据自己的理解总结出了一套适用于现代Web和云技术的微服务实战经验 ...

  5. RESTful API 设计最佳实践

    背景 目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个"万能"的设计标准:如何鉴权?API ...

  6. 我所理解的RESTful Web API [设计篇]

    <我所理解的RESTful Web API [Web标准篇]>Web服务已经成为了异质系统之间的互联与集成的主要手段,在过去一段不短的时间里,Web服务几乎清一水地采用SOAP来构建.构建 ...

  7. (转)Java API设计清单

    转自: 伯乐在线 Java API设计清单 英文原文 TheAmiableAPI 在设计Java API的时候总是有很多不同的规范和考量.与任何复杂的事物一样,这项工作往往就是在考验我们思考的缜密程度 ...

  8. 来自HeroKu的HTTP API 设计指南(中文版)

    原文转自:http://get.jobdeer.com/343.get 来自HeroKu的HTTP API 设计指南(中文版) 翻译 by @Easy 简介 本指南中文翻译者为 @Easy ,他是国内 ...

  9. 移动App的REST API设计实践

    原文:http://www.jianshu.com/p/23cccb3a90b1 通讯协议 一些只是对服务器数据进行CRUD操作的App,通常采用HTTP协议,为了安全也可以采用HTTPS协议.IM软 ...

随机推荐

  1. java web (sevlet)请求之get,post,forward,redirect

    [参考]web请求之get,post,forward,redirect 1,form表单:可以采用post或者get请求,客户端主动跳转,url地址会改变为提交后的地址 2,forward:forwa ...

  2. 【hbase】Unable to read additional data from client sessionid 0x15c92bd1fca0003, likely client has closed socket

    启动hbase ,验证出错 Master is initializing 查看zk日志,发现Unable to read additional data from client sessionid 0 ...

  3. 局域网ARP攻击防护

    通过借助一些安全软件来实现局域网ARP检测及防御功能. A.电脑管家 电脑管家--工具箱--下载ARP防火墙模块 不支持window2003 B.服务器安全狗 Windows版下载:http://fr ...

  4. Spring框架介绍及使用

    Spring框架—控制反转(IOC)1 Spring框架概述1.1 什么是Spring1.2 Spring的优点1.3 Spring的体系结构2 入门案例:(IoC)2.1导入jar包2.2目标类2. ...

  5. Windows10环境下使用VisualSVN server搭建SVN服务器

    参考: Windows10环境下使用VisualSVN server搭建SVN服务器 要搭建个svn用.之前自己的服务器用的乌龟.后来用了这个VisualSVN server. 具体教程见上链接.暂无 ...

  6. 添加ll命令

    $ vim ~/.bashrcalias ll='ls -l'   #加入此行 ps:加入后肯能无法当场起作用,执行该句: source ~/.bashrc

  7. ArcGIS 10.3 AddIN编译旧版本项目问题

    ArcGIS 10.1的AddIN项目,后来ArcGIS版本升级为10.3 AddIN项目想做一些细节调整,结果出生成时没有生成esriaddin文件,ArcMap中AddIn Manager中也没有 ...

  8. 东大oj1155 等凹函数

    Problem Description 定义一种数字称为等凹数字,即从高位到低位,每一位的数字先递减再递增,且该数是一个回文数,即从左读到右与从右读到左是一样的,仅形成一个等凹峰,如543212345 ...

  9. jQuery 实现点击页面其他地方隐藏菜单

    点击页面其它地方隐藏id为messageList的div 代码: $('body').delegate("#message", 'click', function(e) { var ...

  10. 使用hashlib进行登录校验

    注册登录和密码验证 用户注册时,文件中保存用户名,和密码的密文 登录时,密码与文件中的密文进行比较,如果相同就同意登录 import hashlib # 导入模块 def md5(username,p ...