RESTful的正确姿势

RESTfull定义

很多人都有这样的疑问:

  • 什么是RESTful API/RESTful接口?
  • RESTful接口规范是什么样子的?

下面是摘抄自百度百科词条RESTful

REST(英文:Representational State Transfer,简称REST)描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,Roy Fielding是 HTTP 规范的主要编写者之一。在目前主流的三种Web服务交互方案中,REST相比于SOAP(Simple Object Access protocol,简单对象访问协议)以及XML-RPC更加简单明了,无论是对URL的处理还是对Payload的编码,REST都倾向于用更加简单轻量的方法设计和实现。值得注意的是REST并没有一个明确的标准,而更像是一种设计的风格。

REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。

RESTFUL特点包括:
1、每一个URI代表1种资源;
2、客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
3、通过操作资源的表现形式来操作资源;
4、资源的表现形式是XML或者HTML;
5、客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。

RESTful标准用法实践

这里以一个物流系统为例定义一组接口,这里需要完成的功能如下

  • 维护物流运单。用户需要对物流运单进行增删改查操作。每个运单上都有单号,承运商,运输方式等信息。
  • 维护发票信息。用户需要管理发票,每个发票都有重量,金额和相关物流运单号。
  • 统计不同运输方式、承运商在给定时间的费用信息。

通过需求可以清晰的看到两个资源,物流运单和发票,分别定义为delivery和invoice,根据REST风格的特点,定义如下接口

  • 创建物流运单

    REST建议使用POST创建资源

    创建物流运单接口
    URL:[POST]/delivery
    Request: {
    	"doNum": "12345678",
    	"carrier": "shunfeng",
    	"mot": "kongyun",
    	"weight": 234
    }
    Response: {
    	"doNum": "12345678",
    	"carrier": "shunfeng",
    	"mot": "kongyun",
    	"weight": 234
    }
    

错误码:

  • 200: 正常创建

    • 409:单号冲突了
    • 422:参数不正确。仅靠错误码那难以给出更为准确的提示,类似:运输方式kongyun错误,有效运输方式为[Air,Sea,Truck]。
  • 查询物流运单

    REST建议使用GET来获取资源。

    查询所有:
    URL:[GET]/delivery
    Response: [ {运单信息}, {运单信息} ]
    
    根据ID查询
    URL:[GET]/delivery/{delivery-id}
    Response: {运单信息}
    
    多条件查询
    URL:[GET]/delivery/?mot={运输方式}&carrier={承运商}...
    Response: [ {运单信息}, {运单信息} ]
    

    错误码:

    • 200: 正常查询
    • 404: 查询的资源不存在
    • 422: 查询参数不正确

    在实际应用中,这个查询被挑战的最多

    • 参数太多难以传递

      GET传递参数的常规方式就是URL参数,但URL长度是受限的,难以完成类似一次查询1万单号这种需求。

    • 复杂参数难以传递

      GET传递参数的常规方式就是URL参数,但是URL上的参数只能传递结构简单的数据,难以处理对象数组参数,类似[{“carrier”=“c1”, “mot”=“Sea”}, {“carrier”=“c2”, “mot”=“Air”}]这种复杂参数需要特殊处理。对于把文件当做过滤条件的就更处理不了了。

    • 工作量多

      后台在处理复杂查询请求时,一般都会有一个表示请求条件的结构,通过URL参数构造这个结构需要额外工作量,前台在发送请求时,将所有参数拼接到URL中也需要工作量,还面临着各种编码问题。

  • 修改物流运单

    REST建议使用PUT来获取资源。

    修改物流运单接口
    URL:[PUT]/delivery/{delivery-id}
    Request: {
    	"doNum": "12345678",
    	"carrier": "shunfeng",
    	"mot": "kongyun",
    	"weight": 234
    }
    Response: {
    	"doNum": "12345678",
    	"carrier": "shunfeng",
    	"mot": "kongyun",
    	"weight": 234
    }
    

    错误码:

    • 200: 正常修改
    • 422: 参数错误。和创建一样无法报告更为准确的错误。

    在实际应用中,这个修改操作还面临着批量修改和部分修改的挑战

    • 批量修改

      用户希望修改满足条件的所有运单的某些属性为固定值。这个需求很常见,但是REST没说怎么办,当前基本上是开发人员在自由发挥。

    • 部分修改

      复杂的业务数据经常涉及到部分修改功能。比如每抵达一站,都会有一个称重员对货物称重,记录称重结果,这个人不关系目的地一类的信息,他只是在这个运单上增加一个称重记录

  • 删除物流运单

    REST建议使用DELETE来获取资源。

    删除物流运单接口
    URL:[DELETE]/delivery/{delivery-id}
    Response: {
    	"doNum": "12345678",
    	"carrier": "shunfeng",
    	"mot": "kongyun",
    	"weight": 234
    }
    

    实际操作还有批量删除的场景

  • 批量上传物流运单

    REST没建议

  • 批量下载物流运单

    REST没建议

  • 发票管理接口。参照物流运输单来一套就好了。

  • 统计接口

    统计信息是发票和运单的综合数据,我们称他为分摊后发票,是每个发票在单个运单上的金额信息,记为delivery-invoice。在后台可以有一个组件在自动更新。虽然这个接口是一个查询接口,但是查询的内容不是原始数据,是统计数据,不知道应该叫什么URL了。

REST enhance

技术和规范都是服务于业务的,在业务需求的推动下,所有的技术和规范必须与时俱进。针对上面提到的问题,做做了一些调整。

基本规则

  • 不使用HTTP Code返回错误信息

    HTTP Code肩负着传输层的功能,让它兼职业务逻辑,其实很为难它。所有的业务返回信息,都放在Response中更为合理。所以定义了一个通用的返回结构

    {
      "error": [],		// 使用字符串返回所有严重的影响业务继续执行的错信息
      "warning": [],	// 使用字符串返回所有警告类业务信息
      "message": ...	// 根据业务需要返回需要的信息
    }
    
  • 扩展URI指定的资源

    URI仍然用于定位资源,现在资源的范围扩展到原有资源的属性

    /delivery											// 表示运单资源
    /delivery/123									// 表示某一个具体的运单资源
    /delivery/123/weight-check		// 运单123相关的重量检测记录
    /delivery/123/weight-check/1  // 运单123上id为1的重量检测记录
    /delivery/mot									// 这个不是资源,不能这么写
    
  • 通过Head扩展不同的Command

    在原来的4个Method无法表达的情况下,通过Head中增加REST_Command来扩展功能

    REST_Command=BATCH_CREATE		// 表示批量创建请求
    REST_Command=SEARCH       	// 表示当前操作是一个查询请求
    REST_Command=SEARCH_SUMMARY	// 表示查询摘要信息,根据业务需要返回比较少的内容,类似只返回运单基本信息,没有重量检测信息
    REST_Command=PART_UPDATE		// 部分修改。此时修改操作仅仅修改运单数据,不影响重量检测列表
    REST_Command=BATCH_UPDATE		// 批量修改。
    REST_Command=BATCH_DELETE		// 批量删除
    REST_Command=GENERATE				// 对于没有持久化,而是实时计算出来的结果,使用这个命令
    
  • Request为业务参数

    业务参数必须和业务相关才是业务参数,类似用户登录信息,用于标记查询、修改一类的命名,都不是业务信息。

Demo

  • 创建物流运单

    • 创建运单

      URL:[POST]/delivery
      Request: {
      	"doNum": "12345678",
      	"carrier": "shunfeng",
      	"mot": "kongyun",
      	"weight": 234
      }
      Response: {
        "error": [],
        "warning": [],
        "message": {
      	  "doNum": "12345678",
      	  "carrier": "shunfeng",
      	  "mot": "kongyun",
      	  "weight": 234  
        }
      }
      
    • 创建运单子对象

      URL:[POST]/delivery/{delivery-id}/weight-check
      Request: {
      	"id": 1,
      	"time": "2020-02-02 02:02:02",
      	"weight": 234
      }
      Response: {
        "error": [],
        "warning": [],
        "message": {
          "id": 1,
          "time": "2020-02-02 02:02:02",
          "weight": 234
        }
      }
      

      子对象是必须已存与运单的信息,如果没有了运单,这个信息也没有了。运单的重量检测记录,属于子对象,但是分摊到运单的发票不是,没有运单,发票还在呀。

  • 批量创建

    创建物流运单接口
    URL:[POST]/delivery
    Head: REST_Command=BATCH_CREATE
    Request: {	// 这个request可以根据需求做一些定制
    	"doNum": ["12345678", "1234569"],
    	"carrier": "shunfeng",
    	"mot": "kongyun",
    	"weight": 234
    }
    Response: {
      "error": [],
      "warning": [],
      "message": [{
    	  "doNum": "12345678",
    	  "carrier": "shunfeng",
    	  "mot": "kongyun",
    	  "weight": 234  
      }, ...]
    }
    
  • 查询物流运单

    • 查询所有

      URL:[GET]/delivery
      Head: REST_Command=SEARCH;SEARCH_SUMMARY;		// 不同的命令返回数据不同
      Response: {
        "error": [],
        "warning": [],
        "message": [{运单信息}, {运单信息}]
      }
      
    • 根据ID查询单个

      URL:[GET]/delivery/{delivery-id}
      Head: REST_Command=SEARCH;SEARCH_SUMMARY;		// 不同的命令返回数据不同
      Response: {
        "error": [],
        "warning": [],
        "message": {
          "doNum": "12345678",
          "carrier": "shunfeng",
          "mot": "kongyun",
          "weight": 234,
          "weightCheck": [{
            "id": 1,
            "time": "2020-02-02 02:02:02",
            "weight": 234
            }
          ]
        }
      }
      
    • 复杂条件查询

      URL:[POST]/delivery
      Head: REST_Command=SEARCH;SEARCH_SUMMARY;		// 不同的命令返回数据不同
      Request: {
      	"mot": ["haiyun", "kongyun"],
      	"carrier": ["承运商1", "承运商2"]
      }
      Response: {
        "error": [],
        "warning": [],
        "message": [{运单信息}, {运单信息}]
      }
      
    • 查询子对象列表

      URL:[GET]/delivery/{delivery-id}/weight-check/{check-id}
      Response: {
        "error": [],
        "warning": [],
        "message": [{重量检测信息}, {重量检测信息}]
      }
      
  • 修改物流运单

    • 全量修改运单

      URL:[PUT]/delivery/{delivery-id}
      Request: {
      	"doNum": "12345678",
      	"carrier": "shunfeng",
      	"mot": "kongyun",
      	"weight": 234
      }
      Response: {
        "error": [],
        "warning": [],
        "message": {运单信息}
      }
      
    • 部分修改运单

      URL:[PUT]/delivery/{delivery-id}
      Head: REST_Command=PART_UPDATE
      Request: {
      	"doNum": "12345678",
      	"carrier": "shunfeng",
      	"mot": "kongyun",
      	"weight": 234
      }
      Response: {
        "error": [],
        "warning": [],
        "message": {运单信息}
      }
      
    • 修改运单子对象

      URL:[PUT]/delivery/{delivery-id}/weight-check/1
      Request: {
      	"id": 1,
      	"time": "2020-02-02 02:02:02",
      	"weight": 234
      }
      Response: {
        "error": [],
        "warning": [],
        "message": {
          "id": 1,
          "time": "2020-02-02 02:02:02",
          "weight": 234
        }
      }
      
    • 批量修改运单

      URL:[PUT]/delivery/
      Head: REST_Command=BATCH_UPDATE
      Request: {	// 业务自定义请求内容
      	"doNum": ["12345678", "12345679"]
      	"carrier": "shunfeng",
      	"mot": "kongyun",
      	"weight": 234
      }
      Response: {
        "error": [],
        "warning": [],
        "message": [{运单信息}]
      }
      
  • 删除物流运单

    删除操作在删除对象不存在时直接返回,因为期望结果和当前结果一致的。

    • 删除运单

      URL:[DELETE]/delivery/{delivery-id}
      Response: {
        "error": [],
        "warning": [],
        "message": [{运单信息}]
      }
      
    • 删除运单子对象

      URL:[DELETE]/delivery/{delivery-id}/weight-check/{check-id}
      Response: {
        "error": [],
        "warning": [],
        "message": [{重量检测信息}]
      }
      
    • 批量删除

      URL:[POST]/delivery
      Head: REST_Command=BATCH_DELETE
      Request: {
      	"mot": ["haiyun", "kongyun"],
      	"carrier": ["承运商1", "承运商2"]
      }
      Response: {
        "error": [],
        "warning": [],
        "message": [{运单信息}, {运单信息}]
      }
      
  • 批量上传物流运单

    批量上传可以按照批量创建的接口做。更新REST_Command=BATCH_UPLOAD。

  • 批量下载物流运单

    批量下载可以按照批量查询的接口做。更新REST_Command=DOWNLOAD。

  • 统计接口

    统计信息是发票和运单的综合数据,我们称他为分摊后发票,是每个发票在单个运单上的金额信息,记为delivery-invoice。在后台可以有一个组件在自动更新。虽然这个接口是一个查询接口,但是查询的内容不是原始数据,是统计数据,所以命名为delivery-invoice-report。

    URL:[POST]/delivery-invoice-report
    Head: REST_Command=GENERATE
    Request: {	// 自定义查询条件
    	"mot": ["haiyun", "kongyun"],
    	"carrier": ["承运商1", "承运商2"]
    }
    Response: {
      "error": [],
      "warning": [],
      "message": {统计结果}
    }
    

总结

  • URI固定表示资源,子对象也算资源,实时计算的内容使用虚拟资源
  • HTTP Code是传输层的内容,不再参与业务逻辑
  • 使用公共的Response结构来返回各种错误信息
  • 扩展Head,增加REST_Command完成各种多样功能
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值