API 设计最佳实践的思考

1. 原文链接 API 设计最佳实践的思考

2. 正文笔记

API是软件系统的核心,而软件系统的复杂度Complexity是大规模软件系统能否成功最重要的因素。但复杂度Complexity并非某一个单独的问题能完全败坏的,而是在系统设计尤其是API设计层面很多很多小的设计考量一点点叠加起来的

  • 软件系统的核心问题是复杂度的问题,复杂度是逐步积累叠加出来的,而api的设计在应对软件复杂度中占着极为重要的地位。
  • 关键词: 软件复杂度

好的api

  • 提供清晰的思维模型 provides a good mental model
    为什么这一点重要?因为 API 的设计本身最关键的难题并不是让客户端与服务端软件之间如何交互,而是设计者、维护者、API使用者这几个程序员群体之间在 API 生命周期内的互动。一个 API 如何被使用,以及API本身如何被维护,是依赖于维护者和使用者能够对该 API 有清晰的、一致的认识。这非常依赖于设计者提供了一个清晰易于理解的模型。这种状况实际上是不容易达到的。

  • 简单 is simple
    Make things as simple as possible, but no simpler.” 在实际的系统中,尤其是考虑到系统随着需求的增加不断地演化,我们绝大多数情况下见到的问题都是过于复杂的设计,在 API 中引入了过多的实现细节(见下一条),同时也有不少的例子是Oversimplification 引起的,一些不该被合并的改变合并了,导致设计很不合理。

  • 容许多个实现 allows multiple implementations
    这个原则看上去更具体,也是我非常喜欢的一个原则。Sanjay Ghemawat 常常提到该原则。一般来说,在讨论 API 设计时常常被提到的原则是解耦性原则或者说松耦合原则。然而相比于松耦合原则,这个原则更加有可核实性:如果一个 API 自身可以有多个完全不同的实现,一般来说这个API已经有了足够好的抽象,那么一般也不会出现和外部系统耦合过紧的问题。因此这个原则更本质一些。

  • 语义清晰,使得开发者与使用者的思维模型一致。
  • 过于复杂和过于简单都不可取,过于复杂会暴露过多实现的细节,过于简单会表达能力不足,照成调用时逻辑复杂的后果。
  • 当宇哥API可以有多个不同的实现,那么可以说这API将接口与实现分离的很好,不依赖于实现。接口没有暴露具体的实现。
 int open(const char *path, int oflag, .../*,mode_t mode */);
  • File API 已经有几十年历史(从1988年算起,已30年),尽管期间硬件软件系统的发展经历了好几代,这套 API 核心保持了稳定。这是极其了不起的。
  • API 提供了非常清晰的概念模型,每个人都能够很快理解这套API背后的基础概念:什么是文件,以及相关联的操作(open, close, read, write),清晰明了;
  • 支持很多的不同文件系统实现,这些系统实现甚至于属于类型非常不同的设备,例如磁盘、块设备、管道(pipe)、共享内存、网络、终端 terminal 等等。这些设备有的是随机访问的,有的只支持顺序访问;有的是持久化的有的则不是。然而所有不同的设备不同的文件系统实现都可以采用了同样的接口,使得上层系统不必关注底层实现的不同,这是这套 API 强大的生命力的表现。
  • 通过 资源 + 操作的方式定义,对其的具体实现留给实际开发者,接口成面不暴露实现细节,

如何实现好的api

  • Document well 写详细的文档
    准确的记录每个字段和每个方法,并且保持更新
  • Carefully define the “resource” of your API 仔细的定义“资源”
  • 如果适合的话,选用“资源”加操作的方式来定义。今天很多的 API 都可以采用这样一个抽象的模式来定义,这种模式有很多好处,也适合于 HTTP 的 RESTful API 的设计。但是在设计 API 时,一个重要的前提是对 Resource 本身进行合理的定义。什么样的定义是合理的? Resource 资源本身是对一套 API 操作核心对象的一个抽象Abstraction。
  • 抽象的过程是去除细节的过程。在我们做设计时,如果现实世界的流程或者操作对象是具体化的,抽象的 Object 的选择可能不那么困难,但是对于哪些细节应该包括,是需要很多思考的。例如对于文件的API,可以看出,文件 File 这个 Resource(资源)的抽象,是“可以由一个字符串唯一标识的数据记录”。这个定义去除了文件是如何标识的(这个问题留给了各个文件系统的具体实现),也去除了关于如何存储的组织结构(again,留给了存储系统)细节。
  • 虽然我们希望API简单,但是更重要的是选择对的实体来建模。在底层系统设计中,我们倾向于更简单的抽象设计。有的系统里面,域模型本身的设计往往不会这么简单,需要更细致的考虑如何定义 Resource。一般来说,域模型中的概念抽象,如果能和现实中的人们的体验接近,会有利于人们理解该模型。选择对的实体来建模往往是关键。
  • Choose the right level of abstraction 选择合适的抽象层
  • 从哪个角度看待接口
  • Naming and identification of the resource 命名与标识

当 API 定义了一个资源对象,下面一般需要的是提供命名/标识( Naming and identification )。在 naming/ID 方面,一般有两个选择(不是指系统内部的 ID,而是会暴露给用户的):

  • 用free-form string作为ID(string nameAsId)
  • 用结构化数据表达naming/ID

何时选择哪个方法,需要具体分析。采用 Free-form string 的方式定义的命名,为系统的具体实现留下了最大的自由度。带来的问题是命名的内在结构(如路径)本身并非API强制定义的一部分,转为变成实现细节。如果命名本身存在结构,客户端需要有提取结构信息的逻辑,这是一个需要做的平衡。

  • 前者有更大的灵活度,但因为不体现数据结构,所在在实现的时候处理逻辑会复杂一些。后者因为是结构化的数据所以具有更好的交互性,但也存在暴露实现细节的风险,这样不利于接口的通用性。
  • Conceptually what are the meaningful operations on this resource? 对于该对象来说,什么操作概念上是合理的?
  • 这里考虑更多的是概念上的合理性,用一一个词概述就是自然
  • For update operations, prefer idempotency whenever feasible 更新操作,尽量保持幂等性
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值