design\project\学习 API Design 读书笔记

API 设计

一、API 设计基础

原文链接 Designing Quality APIs

明确 API 设计目标是什么?

当人们在技术选择上存在分歧时,往往是因为他们在目标上不一致

  • 如何确定你的目标?抓重点

  • 你的工作中遇到的最关键的问题是什么?如果不能抓住关键问题,那就不算出色的工作。为了找到解决方案,你必须时刻盯着关键问题

API 设计的基础定义(交互过程)
  • 分为客户端和服务端

  • 客户端通过调用服务的的 action 获得数据

  • 交互必须快速、简单、安全

整个互联网都是建立在 URI 的基础之上的

API 的复杂性
  • 软件通常在遇到变更(change)时,难于修改

    • 软件本身功能点多,功能复杂性

    • 软件建立在一定的假设的前提上,这些前提很容易变更,包括:

      • 从业务视角来看,我们的业务需求持续变更

        • 存在老系统变更

        • 业务需求变更

        • 业务场景变更

      • 从技术实现视角来看,我们总是追求更好的解决方案

        • 选用的数据库变更

        • 选用的实现技术

        • 选用的程序语言变更

  • 软件集成(integrate)复杂问题,集成通常是多维度的、海量的

    • 有些系统,缺失基础的 API 造成集成复杂

    • 每个系统有自己独立的 API ,为了集成,你需要全部学习这些 API,(n 的平方 - n) 造成集成复杂

关键 API
  • 尽管很复杂,区分一些解决关键问题的: 关键 API

  • 其他 API 只负责模块间的通信

主要的 API 设计风格(style)
  • 实体 CRUD 接口 (Entity-oriented),又叫 Rest 风格接口

    • 使用代码操纵另一个模块暴露的实体(通过操作实体,改变行为)

    • 栗如:

      {
              
              
          type: "/log",
          ...
      }
      POST       /logs
      GET        /logs/1
      DELETE     /logs/1
      ...
      
  • 功能(behavier)接口(Procedure-oriented)

    • 使用代码调用另一个模块的功能接口(通过行为,修改实体)

    • 栗如:

      // 通常会有很多接口配合使用
      /getAllLogs/getAllSuccessLogs/verrifyd
      /verifyLogLocation/locationNeeded
      /locationNeeded/createLocation
      /createLocation/getPlatform
      ...
      
  • Rest 风格接口便于统一管理,避免每个系统有自己的 API 风格,降低学习成本

    • 但不要和你的后端数据模型一致,每个 Entity 包含多个 Table 数据模型

    • 只是使用统一的 API 定义风格

关键 API ,API 属性选取
  • 全应用统一的 API 设计风格

  • 统一的传输模型(分布式系统通信)

  • 自由的扩展(free to implementation assumptions)

统一的 API 设计风格
  • RestAPI 的矛盾

    • restAPI 希望:摒弃 HTTP 之上的 web 层概念,用 HTTP 解决一切。

    • 但是RestAPI 没有必需的,查询或版本(version)的处理

  • 如何设计统一风格的 API

    • 定义通用的,有实际意义的 URL,就和定义表名一样。栗如:/customers,/accounts,/orders

    • 定义通用的,有实际意义的资源 schema

    • 定义其他复杂接口时,不得扰乱这些基础的定义

  • 示栗:

    {
          
          
        // 常见错误:该接口缺乏应用程序标识 `logs`
        id: "12345",
        type: "/log",
        ...
    }
    
    {
          
          
        // 为 ID 提供应用程序标识
        id: "/logs/12345",
        type: "/log",
        ownerID: "98765"
        ...
    }
    {
          
          
        id: "/logs/12345",
        type: "/log",
        // 错误:如果 ownerID 不是 person 而是机构?
        ownerID: "98765"
        ...
    }
    {
          
          
        id: "/logs/12345",
        type: "/log",
        // 指定 identity 为一个 URI
        owner: "rpc的url/98765"
        ...
    }
    {
          
          
        id: "/logs/12345",
        type: "/log",
        // 不合适,identity 指定为,可能会修改的 name
        owner: "rpc的url/name"
        ...
    }
    

应用集成的最基本问题是:存储在不同的应用程序中的实体之间的转换

一个最主要的原因是:缺乏应用程序标识,数据 identity12345 依赖于 log 应为 log.12345ownerID 依赖于 URI

  • 如何设计 query API

    https://domain.com/person/Joe/pets/Lassie
    

    改进为:(query API 多次调用会得到不同的结果)

    https://domain.com/search?type=/dog&name=Lassie&owner=(name=Joe)
    
  • 当设计非 关键 API 时:重点考虑:效率高,易于解析,与编程语言匹配

  • 当设计 关键 API 时:应考虑独立于现有技术和使用模式,考虑其适应变化能力

  • 统一的 API 设计其他原则

    • 不要传递包含不同的类型数据的集合,这样不便于解析。栗如:List<Object>

    • 接口应该传递预先定义好的属性(closed content)

    • 不要传递索引集,栗如:

      // 使用 userId 做为索引组织数据,限定了返回格式,不能适用未来变化 open to change
      {
              
              
          user-id-a:
          {
              
              
              data:
              {
              
              
                  id: "xxx",
                  picture: "photo-url",
                  createTime: "xxx",
                  ...
              }
          },
          user-id-b:
          {
              
              
              data: {
              
              ...}
          }
      }
      

      应改为平凡的(flat)

      [
          {
              
              
              id: "xxx",
              owner: "user-id-a",
              picture: "photo-url",
              createTime: "xxx",
              ...
          },
      ]
      
    • 不要传递动态类型,可为 String 可为 Object

    • 避免值为 null/not-null

    • 不要让数据存在歧义

    • 不要使用 boolean 来代表 3 个值

  • 使用简单格式的 JSON

    • 简单格式 JSON 对象都必须对应于 API 数据模型中的一个实体

    • 定义了一个名为 self 的特殊键值对,对应的数据模型实体

      // 错误示栗
      {
              
              
          "properties": 
          {
              
              
              "name": "Martin",
              "bornOn": "1957-01-05"
          },
          "links": 
          [{
              
              
              "rel": "bornIn",
              "href": "http://www.scotland.org#"
          }]
      }
      
  • 版本

    • 实体版本(新的版本,新的功能)

    • 实体可以有多种格式,实体格式版本(同一个内容的不同属性别名)

    • 历史版本(版本回退等)

  • 不要在 url 中添加 版本号

    {
          
          
        id: "/xxx/12345",
        name: "Lassie",
        owner: "yyy/98765"
    }
    

    不要使用

    // 因为不是所有的实体都有 v2 版本,/v2/Lassie 将得不到值,服务器不好处理
    {
          
          
        id: "/v1/xxx/12345",
        name: "Lassie",
        owner: "/v1/yyy/98765"
    }
    
  • 每个实体必须有一个直接访问的无版本号(version-free)的 URI

  • 允许客户端使用指定格式请求指定版本

  • 选择性的提供指定版本的 URL

结论
  • 如果相互耦合的模块,需要高效的交互,考虑 gRPC

  • 如果需要为适用未来变化,做集成,直接使用 HTTP、JSON

二、API 设计基本原则

  • 封装
    • 信息隐蔽原则
      • API 应隐藏内部细节
  • 单一职责原则
    • 考虑 API 的外部组织形式的内聚性
  • 接口隔离原则 ISP
    • 考虑方法的粒度(实体接口),功能范围(功能接口)
  • 迪米特法则
    • 减少业务耦合,提升内聚性
      • 考察调用其他模块、被其他模块调用时的耦合
  • 开闭原则
    • 尽量使用分布式 消息对象 输入输出
  • 易用性原则
    • 符合大众习惯,减少上手成本
  • 其他
    • 取个好的名字
    • 非关键 API (一般为实体接口,见上文)统一风格
    • 做好 API 版本管理,兼容
    • 之类的 ………………

猜你喜欢

转载自blog.csdn.net/u012296499/article/details/123283113