zoukankan      html  css  js  c++  java
  • REST API设计实践

    1 RESTful

    1.1 什么是RESTful

    官方解释

    Representational State Transfer 的简称,即 表现层状态转移。

    人看的解释

    • REST指一组架构约束条件和原则, 如果一个架构符合 REST 的约束条件和原则,就称之为 RESTful 架构
    • RESTful 是一种软件架构风格,而不是标准

    大神总结

    • URL定位资源
    • HTTP动词(GET, POST, DELETE, PUT)描述操作
    • HTTP状态码表示响应结果

    总的来说,RESTful是一种web服务设计风格,风格意思就是大家默认的但不是强制的。

    1.2 REST规范

    用URL定位资源

    REST 的主体是资源,所谓“资源”,就是网络上的一个具体信息,例如:一张图片,一段文字、一种服务。总之就是一个实际存在的东西,而 URL 就是用来指向这个资源的。

    例如:

    https://api.example.com/users
    

    用HTTP动词描述操作

    对资源的操作,无外乎 CRUD(增删改查),RESTful 中,每个 HTTP 动词对应一个 CRUD 操作。

    • GET:对应 Retrieve 操作。GET请求从不改变资源的状态。无副作用。GET方法是幂等的。GET方法具有只读的含义。因此,你可以完美的使用缓存。
    • POST:对应 Create 操作
    • DELETE:对应 Delete 操作
    • PUT:对应 Update 操作

    用HTTP状态码表示响应结果

    RESTful Web服务应使用合适的HTTP状态码来响应客户端请求

    • 2xx - 成功 - 一切都很好
    • 4xx - 客户端错误 - 如果客户端发生错误(例如客户端发送无效请求或未被授权)
    • 5xx – 服务器错误 - 如果服务器发生错误(例如,尝试处理请求时出错)

    2 RESTful API设计实践

    设计RESTful API可能很棘手,因为没有官方和强制标准。基本上,实现API的方法有很多种,但其中一些方法已在实践中得到证明并被广泛采用。这篇文章涵盖了构建 HTTP 和 RESTful API 的最佳实践。我们将讨论 URL 结构、HTTP 方法、创建和更新资源、设计关系、有效负载格式、分页、版本控制等等。

    2.1 URL设计建议

    用名词代替动词表示资源

    这让你的API更简洁,URL数目更少

    正确示例:
    GET /employees
    GET /employees?state=external
    POST /employees
    PUT /employees/56
    
    错误示例:
    /getAllEmployees
    /getAllExternalEmployees
    /createEmployee
    /updateEmployee
    

    资源用复数表示,全部小写,用_或-连接,不要使用驼峰命名法

    这是个人爱好问题,但复数形式更为常见。最重要的是:避免复数和单数名词混合使用,这显得非常混乱且容易出错。

    推荐:
    
    /employees
    /employees/21
    
    不推荐:
    
    /employee
    /employee/21
    

    每个资源使用两个URL

    资源集合用一个URL,具体某个资源用一个URL

    /employees         #资源集合的URL
    /employees/56      #具体某个资源的URL
    

    在URL中强制加入版本号

    从始至终,都使用版本号发布您的RESTful API。将版本号放在URL中以是必需的。如果您有不兼容和破坏性的更改,版本号将让你能更容易的发布API。发布新API时,只需在增加版本号中的数字。这样的话,客户端可以自如的迁移到新API,不会因调用完全不同的新API而陷入困境。使用直观的 “v” 前缀来表示后面的数字是版本号。

    你不需要使用次级版本号(“v1.2”),因为你不应该频繁的去发布API版本。

    正确示例:
    /v1/employees
    
    错误示例:
    /v1.2/employees
    

    提供分页信息

    一次性返回数据库所有资源不是一个好主意。因此,需要提供分页机制。通常使用数据库中众所周知的参数offset和limit。

    /employees?offset=30&limit=15       #返回30 到 45的员工
    

    如果客户端没有传这些参数,则应使用默认值。通常默认值是offset = 0和limit = 10。如果数据库检索很慢,应当减小limit值

    /employees       #返回0 到 10的员工
    

    此外,如果您使用分页,客户端需要知道资源总数。例: 请求:

    GET /employees
    
    响应:
    
    {
      "offset": 0,
      "limit": 10,
      "total": 3465,
      "employees": [
        //...
      ]
    }
    

    对可选的、复杂的参数,使用查询字符串(?)

    为了让你的URL更小、更简洁。为资源设置一个基本URL,将可选的、复杂的参数用查询字符串表示。

    正确示例:
    
    GET /employees?state=internal&maturity=senior
    
    错误示例:
    
    GET /employees
    GET /externalEmployees
    GET /internalEmployees
    GET /internalAndSeniorEmployees
    

    非资源请求用动词

    有时API调用并不涉及资源(如计算,翻译或转换)。

    GET /translate?from=de_DE&to=en_US&text=Hallo
    
    //Trigger an operation that changes the server-side state
    POST /restartServer
    //no body
    
    POST /banUserFromChannel
    { "user": "123", "channel": "serious-chat-channel" }
    

    在这种情况下,不涉及任何资源,服务器执行一个操作并将结果返回给客户端。因此,您应该在 URL 中使用动词而不是名词来清楚地区分操作(RPC-style APIs)和用于领域建模的资源(REST APIs)。

    • 如果一个API已操作为主,应该设计成RPC风格
    • 如果一个API主要用来对相关数据进行CRUD,应该设计成REST风格

    2.2 HTTP动词设计建议

    HTTP动词语义

    • Get
      • 幂等
      • 只读。GET永远不会改变服务器端资源的状态,它必须没有副作用。
    • PUT
      • 幂等
      • 可用于创建和更新
      • 常用于更新(完全更新)。例如:PUT /employees/1- 更新员工 1
      • 始终在请求中包含整个有效负载。要么全有要么全无。PUT不用于部分更新
    • POST
      • 非幂等
      • 用于创建。例如:POST /employees创建一个新员工
    • DELETE
      • 幂等
      • 用于删除。例如:DELETE /employees/1
    • PATCH
      • 幂等
      • 用于部分更新。例如:PATCH /employees/1- 使用负载中包含的字段更新员工1, 员工1的其他字段不会被更新

    2个URL乘以4个HTTP方法就是一组很好的功能。看看这个表格:

    POST(创建) DELETE(删除) PUT(更新) GET(查询)
    /employees 创建一个新员工 删除所有员工 批量更新员工信息 列出所有员工
    /employees/56 (错误) 删除56号员工 更新56号员工的信息 获取56号员工的信息

    2.3 HTTP状态码设计建议

    RESTful Web服务应使用合适的HTTP状态码来响应客户端请求,其中的大部分HTTP状态码都不会被用到,只会用其中的一小部分。通常会用到一下几个:

    2xx:成功 3xx:重定向 4xx:客户端错误 5xx:服务器错误
    200 成功 301 永久重定向 400 错误请求 500 内部服务器错误
    201 创建 304 资源未修改 401未授权
    403 禁止
    404 未找到

    2.4 响应内容建议

    使用小驼峰命名法

    使用小驼峰命名法作为属性标识符。

    { "yearOfBirth": 1982 }
    

    不要使用下划线(year_of_birth)或大驼峰命名法(YearOfBirth)。通常,RESTful Web服务将被JavaScript编写的客户端使用。客户端会将JSON响应转换为JavaScript对象(通过调用var person = JSON.parse(response)),然后调用其属性。因此,最好遵循JavaScript代码通用规范

    将实际数据包装在一个data字段中

    GET响应,PUT、POST 和 PATCH 请求体都应该将实际数据包装data字段

    • 有剩余空间可以添加元数据(例如用于分页、链接、弃用警告、错误消息)
    • 保持数据一致性
    • JSON:API 标准兼容
    GET /employees  # 返回data字段中的对象列表
    
    {
      "data": [
        { "id": 1, "name": "Larry" }
        , { "id": 2, "name": "Peter" }
      ]
    }
    
    GET /employees/1 # 返回data字段中的单个对象
    
    {
      "data": { 
        "id": 1, 
        "name": "Larry"
      }
    }
    

    返回有用的错误信息

    除了合适的状态码之外,还应该在HTTP响应正文中提供有用的错误提示和详细的描述。这是一个例子:

    请求
    
    GET /employees?state=super
    
    响应
    
    // 400 Bad Request
    {
      "errors": [
        {
          "status": 400,
          "detail": "Invalid state. Valid values are 'internal' or 'external'",
          "code": 352,
          "links": {
            "about": "http://www.domain.com/rest/errorcode/352"
          }
        }
      ]
    }
    

    提供用于导航的链接

    理想情况下,您不要让您的客户构建 URL 以使用您的 REST API。让我们考虑一个例子。

    客户想要访问员工的工资报表。因此,他必须知道他可以通过将查询参数附加salaryStatements到员工 URL(例如/employees/21/salaryStatements)来访问工资报表。这种字符串连接容易出错、脆弱且难以维护。如果您更改访问 REST API 中的薪水报表的方式(例如,现在使用“salary-statements”或“paySlips”),所有客户端都将中断。

    {
      "data": [
        {
          "id":1,
          "name":"Paul",
          "links": [
            {
              "salary": "http://www.domain.com/employees/1/salaryStatements"
            }
          ]
        }
      ]
    }
    

    如果客户端完全依赖链接来获取工资单,那么即使您更改 API,他也不会中断,因为客户端将始终获得有效的 URL(只要您在 URL 更改的情况下更新链接)。另一个好处是您的 API 变得更具自我描述性,并且客户不必经常查找文档。

    适当的设计关系

    让我们假设每个employee都有 amanager和几个teamMembers。在API中设计关系基本上有三个常用方式:链接、外键和嵌入。

    它们都是有效的,正确的选择取决于用例。主要考虑

    • 客户端的访问模式
    • 可容忍的请求次数
    • 有效负载大小
    链接:负载小,但要获取完整数据访问次数过多
    
    {
      "data": [
        { 
          "id": 1, 
          "name": "Larry",
          "relationships": {
            "manager": "http://www.domain.com/employees/1/manager",
            "teamMembers": [ 
              "http://www.domain.com/employees/12",
              "http://www.domain.com/employees/13"
            ]
            //or "teamMembers": "http://www.domain.com/employees/1/teamMembers"
          }
        }
      ]
    }
    
    外键:负载大小适中,没有重复,但用户需要解析数据,使用麻烦
    
    {
      "data": [
        { 
          "id": 1, 
          "name": "Larry",
          "relationships": {
            "manager":  5 , 
            "teamMembers": [ 12, 13 ]
          }
        }
      ],
      "included": {
        "manager": {
          "id": 5, 
          "name": "Kevin"
        },
        "teamMembers": [
          { "id": 12, "name": "Albert" }
          , { "id": 13, "name": "Tom" }
        ]
      }
    }
    
    嵌入:用户获取数据最方便,但负载过大,引用数据会重复
    
    {
      "data": [
        { 
          "id": 1, 
          "name": "Larry",
          "manager": {
            "id": 5, 
            "name": "Kevin"
          },
          "teamMembers": [
            { "id": 12, "name": "Albert" }
            , { "id": 13, "name": "Tom" }
          ]
        }
      ]
    }
    

    3 One More Things

    3.1

    参考资料


  • 相关阅读:
    Solidworks如何设置零件材料,如何评估零件质量
    Office 如何双面打印Word文档
    Discuz常见小问题2-如何在新建的页面上只显示一部分板块
    Discuz常见小问题2-如何在数据库搜索指定关键字
    Discuz常见小问题2-如何修改整个网站的默认字体为微软雅黑
    Discuz常见小问题2-如何修改管理员密码,修改admin账户密码
    php使用include报错require_once(../include.php): failed to open stream: No such file or directo
    PHP中的__get()和__set()方法获取设置私有属性
    php->是什么意思
    PHP中require(),include(),require_once()和include_once()有什么区别
  • 原文地址:https://www.cnblogs.com/hello-zy/p/15070568.html
Copyright © 2011-2022 走看看