Pulpcode

捕获,搅碎,拼接,吞咽

0%

如何设计好一个协议

最近一直在从设计到编码,一直在想什么是一个好的设计,以下是开发中的一些心得。

几类接口

一开始我觉得我的服务有很多的接口,但是在后来我才发现,我的接口基本上就是三类(实际上应该有四类,但是目前只有三类,本文后面的部分会介绍还有哪三类)。

我的服务

我的服务基本上有这样几个功能,我可以接收一笔订单,我的后台负责处理这笔订单。我还可以接收一个查询,返回一笔订单的状态, 还可以接收别的系统发来的通知,算是其它系统告诉我我对其它系统发出请求后的处理结果。这就是我指的三类接口。(以下的描述中,“我”是服务商,“你”就是请求方)

提交(submit)

你在提交一笔订单的时候,由请求方你的流水号来唯一标识这笔订单,不能重复提交。我会告诉你提交结果,提交成功之后,我还会返回给你一个流水号。并且告诉你一个你的订单此时此刻的状态(成功,或者处理中),这里需要注意的是,提交结果,和订单状态是分开的,提交结果是标识提交这个动作是否成功,订单状态则是标识这个单子的状态。可以这么理解,一个是传输级别的,一个是业务级别的。你的接口,一定要注意有这两个标识。

查询(query)

你可以通过你的唯一流水号,(为什么不通过我的流水号来查询,实际上也可以通过我的流水号来查,但是要考虑到断连的情况,这个时候你虽然提交了,但是并不知道我的流水号。)类似于提交,我即会告诉你,你这个查询动作是否成功,也会告诉你这个订单的状态(具体的查询结果)。

通知(notify)

我可能会通过你对我接口发送的通知,来修改我的服务的状态。我不会告诉你,订单结果,(因为你只是通知一件事,对事情造成的影响并不关心),但是我会告诉你,你通知成功了,或者通知失败了。按照约定的流程,如果通知失败了,你应该还会给我重发。所以我的通知接口,一定要有放重发功能,也就是发一次,和发十次的结果应该是一样的。

实际上,你会发现,这三个接口,查询是不会修改我的数据库字段状态,但是提交会增加一条数据,通知会修改一条数据。说到这里,你其实发现,这三个接口,对应数据库的。不过这是数据库的接口,如果映射到协议上,比如http协议,这就是POST,GET,PUT。

POST和PUT

实际上,大多数人写的http服务,都是GET,POST乱用,而PUT又几乎不用。你如果看了什么垃圾书,还会告诉你,get是通过url传递参数的,而post是通过body传递的,更安全。

但是你看过http的设计,就会明白,GET是不会修改服务器状态的(所以如果你的get请求如果能修改服务器状态,那你的服务一定有问题。),POST和PUT会修改,但是PUT是幂等性的,幂等性是指重复使用同样的参数调用同一方法时总能获得同样的结果。所以你提交十次POST和提交一次POST的效果是不一样的,但提交十次PUT和提交一次PUT的效果是一样的。(类似于insert操作执行十次与update操作执行十次的效果。)
这里没有提到DELETE操作,实际上数据库的DELETE操作对应了http协议中的DELETE操作,但是我的接口实在是没有一个面向DELETE,所以也没有讨论。

看到这里你就明白通知接口是可以被多次提交了,需要你自己的业务逻辑判断是否算接受成功,是否要修改服务器状态,是否可以被重发。

签名

协议之间的通信,一般要保证,

  1. 这个消息确实是由你发起的,不可抵赖。
  2. 这个消息还是你发起时的样子,没有被修改。

一般由签名来保证这两项,实现方式有如下几种:

MD5签名

将需要签名的数据,尾部追加双方协定的秘钥(类似于只有双双方才知道的盐),再用MD5进行签名。(缺点,没有像rsa那样,能够一对多。)

SHA1WIithRSA

这就是经典的数字签名了。首先用sha1来hash将要签名的数据,然后在用RSA的私钥来对数据进行签名,最后在对这堆字节进行Base64转化为字符串。当然MD5WithRSA也是可以的。

关于签名和rsa的细节知识,这里不进行详细说明,毕竟不是此篇博客的重点,请参见我的其它文章,有详细介绍。

当然还有一些特殊的协议是要保证数据在传输的时候是不可见的。这就是加密协议了,一般走https就可以了。或者可以用数字信封。

例子

说了这么多,这里举一个例子,来看看我的应用中几个协议到底长得什么样。(所有协议走的json)

提交请求

1
2
3
4
5
6
7
8
9
10
11
12
{
"method": "api 接口名称",
"timestamp": "时间戳",
"app_id": "请求方ID",
"version": "协议版本号",
"sign": "签名",
"payload":{
"serial_number": "xxx",
"amt": xxx,
..........
}
}

注意json串的外部,算是传输层的描述,而内部算是业务层的。我看了支付宝和银行的一些文档,都会有时间戳和app_id,这些关键字段。

还要注意的是提交

返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"success": "T",
"error_code": "xxx",
"error_msg": "xxx",
"sign": "xxx",
"payload":
{
"serial_number": "xxx",
"qpg_id": "xxx",
"status": xxx
}
}

在这个协议中,有success字段,标识提交结果,还有error_code和error_msg标识错误码和错误描述,这个错误码算是给机器看的,错误描述是给人看的,而且更细节。

我会告诉你,你的请求流水号和我系统内部的流水号,包括此单的状态。

查询和通知的标准字段基本类似,通知返回没有太多信息,只会告诉你通知是否成功,如果错误,错误的原因是什么。(如果我告诉你你通知错误,那你一定是要重发的)

最后在一个,你在写协议文档的时候,表格的格式(这是从支付宝抄袭来的)。

1
名称	类型	是否必填	示例值  	默认值 	描述