一个真实的反模式 API 设计案例…
最近在做一个点餐应用,使用了第三方的云打印设备,通过 http 提交订单的方式,控制打印机的打印。大致流程如下:
- 调用方发送打印请求到设备厂商的云后台,云后台会生成并返回一个打印任务id。
- 云后台讲打印任务入列,排队发送给对应的打印机。
- 打印机将打印结果返回云后台。
- 云后台讲打印机结果推送给调用方。
设备厂商的开放平台给了两个接口.
提交打印订单:
user 必须 string 飞鹅云后台注册用户名。
stime 必须 int 当前UNIX时间戳,10位,精确到秒。
backurl string 回调地址。
expired int 失效时间。
sig 必须 string 签名。
打印状态回调:
orderId string 订单ID
status int 订单状态
stime int UNIX时间戳
sign string 数字签名
这里面就出现问题了:第二个接口不仅在流程上依赖第一步,在数据上也依赖第一步。
第一步在流程上看起来是个同步操作,但数据实际上依旧是异步。如果第一步没有成功获取到orderId(比如连接成功但是读取超时),但是由于云服务器已经收到了订单,因此,打印任务会照常进行,回调也会正常收到。不过,本次回调会变成一个无效的回调,因为 orderId 是无法理解的。由于接口不是幂等的,假如在这种情况下进行常规的重试,就有可能会导致同一个订单被重复打印。
所以,开放平台可以类比电商里面的“下单”与“支付”步骤,考虑几种处理方式:
- 在提交订单之前,增加一个“预下单”的步骤。这样一个订单就只会对应一个打印任务。
- 提供一个允许调用方自定义的识别码,并在回调的同时原样返回,把控制权交回给调用方,才能达成信息的一致。