API设计秘籍:从入门到精通

247 阅读9分钟

当我们着手构建一套高效的 HTTP API 时,首要任务是确立清晰的 API 协议,这包括对 API 的命名规则和功能规范的明确。一个恰当的命名策略不仅有助于清晰地传达接口的功能,还能促进开发人员对接口目的的快速把握。同时,一套完善的功能规范是确保接口的通用性和稳定性的关键。接下来,我将从四个关键方面——API 风格、API 的单一职责原则、API 文档管理和 API 版本控制——来阐述如何打造一套卓越的 HTTP API。

API 的风格

在众多 API 风格中,REST 和 SOAP 是业界广泛采用的两种。SOAP 依赖于 XML 格式进行消息传递,而 REST 则基于标准的 HTTP 协议,并引入了一系列规范和约束。REST 特别适用于客户端与服务器之间的交互,其设计简洁且层次分明,因此在互联网公司中,我们更倾向于采用 REST 风格来构建 HTTP API。

REST 并非一项标准,而是一种设计原则和约束的集合。在遵循 REST 风格设计 API 时,我们需要遵守三个基本原则:将业务模型抽象为资源,确保每个资源都有一个唯一的标识符 URI,以及通过标准的 HTTP 请求方法来操作服务器端资源,实现状态的转换。

在面对具体需求时,如何设计 REST 风格的 API 呢?首先,识别资源,每个 URI 通常对应领域模型中的一个实体。URI 的命名应遵循一定规范,如全部使用小写字母,单词间用下划线分隔,集合名词使用复数形式,参数列表进行编码等。

其次,使用标准的 HTTP 方法来表示对资源的操作,例如使用 GET 请求查询资源,POST 请求创建资源,PUT 请求更新资源,DELETE 请求删除资源。

GET           /api/{资源}/{id}
POST          /api/{资源}
PUT           /api/{资源}/{id}
DELETE        /api/{资源}/{id}

除了上面两个步骤,我们还要确保 API 的幂等性,什么是幂等性呢?幂等性就是使用同样参数对同一个 API 调用一次或多次,对资源状态改变的效果是等价的。但是幂等性不保证反复请求能拿到相同的 Response。以 DELETE 为例,第一次 DELETE 返回 200 表示删除成功,第二次返回 404 提示资源不存在,这是允许的。

总之,识别出资源,我们就能知道该赋予资源哪些操作,从而就能够设计出 REST 风格的 API,同时保证团队 API 风格的统一。

API 的单一职责

设计良好 HTTP API 的第二个关键点是 API 的单一职责原则。单一职责原则意味着每个 API 应该只执行一个独立的功能。。那怎么理解这个 API 单一职责原则呢?我们假设有一个在线答题系统,其中有一个接口用来提交答案,但是这个接口的开发人员在提交答案后,还在其中做了一个发送成绩邮件的功能。这样做往往会带来两类问题:第一,修改了其中某些代码,就破坏了发送邮件的功能;第二,某一天邮件不发送了,排查人员通过接口名很难找到相关代码在哪里。这两类问题就是职责不单一导致的。

我们要知道,在平常的工作中可能会遇到这样的接口,它调用的接口中包含了相当多的功能实现,然而这些功能并不一定密切相关,如果其中一个功能需求改变了,修改这个功能就有可能破坏其他几个功能。如果一个接口只包含一个独立功能,这样功能之间的业务演进就相对独立,不受影响。坚持每个 API 的功能单一,只干一件事,这就是最简单的单一职责原则。

说了这么多,我们到底该怎么保证 API 的职责是单一的呢?有两个方法,第一,我们前面已经讲过设计 REST API 的第一步就是要识别领域模型中的资源,而在服务端的表现形式一般就是实体类,那么定义好接口之后,我们回头看看这个接口的功能实现与相应的实体类的相关度,以此可以再次确认开发好的接口是不是只干了一件事,这是保证接口职责单一的一个有效方法。

第二个方法,我们首先识别出接口的行为集合,以及这些行为是不是一件事,如果不是一件事,那么可以将这些不同的行为封装到不同的类或模块中,职责自然而然就变得单一了。

为了加深理解,我们举个例子模拟实践一下。我们以学生为资源写出一套 CRUD 的 API,相应地,会有一个学生类,包含了学生的个人信息,比如姓名、性别、身高、体重、血型、党团关系等,这些特性都面向学生这个对象,满足单一职责原则。假设在插入学生信息的接口,同时帮学生完成了选课,那么这个接口就需要使用学生类,以及课表类,还有一些相应的行为类。这时,我们会发现在一个接口中使用了多个并不那么密切相关的实体类,所以,这个接口就没有做到职责单一,这时怎么办呢?我们可以将插入学生信息的接口拆分为 2 个接口,一个负责插入学生信息,另一个负责选课。这样,不管将来课程表怎么变化,都不会影响到学生信息的接口。

总之,我们开发 API 时,一定要搞清楚 API 干的事情是不是同一件事,如果涉及到不同的功能行为,那么,请根据功能拆分 API。

当团队开发的 API 越来越多,导致沟通代价越来越高,我们就需要统一管理这些 API,做到接口可查、可测。这时就要借助一些 API 文档管理工具管理这些 API 以便测试或者开发人员查询、使用。一份完整的接口文档也是整个开发团队的宝贵资源。那么,我们该怎么选择 API 文档管理工具呢?

API 的文档管理

随着 API 数量的增加,统一管理 API 变得至关重要。选择一款合适的 API 文档管理工具,如 ShowDoc、Swagger 或 YApi,可以帮助我们更有效地管理和共享 API 文档。ShowDoc 提供了基本的展示功能,需要手工录入;Swagger 提供了自动化生成 API 文档的功能;而 YApi 则提供了自定义 Mock 数据和自动化测试集合的功能。

API 的版本控制

API 在共享给团队其他成员协作开发并上线后,产品迭代引发的系统变动是不可避免的。API 如何兼容不同版本的客户端成为一个挑战。软件客户端通常会有很多版本,相应地,服务端的 API 也需要版本控制来兼容和支持所有客户端版本。通常有两种方法来做到这一点:第一种方法是保留老接口,对新版本增加新接口。这种方法的优点是职责很单一,缺点是会带来一些代码重复;第二种方法是使用同一个接口,使用一个版本参数来标识版本信息,在接口实现中对不同版本做出不同处理,这种方法的优缺点和第一种方法刚好相反。

我们在实际工作场景中,使用哪种方法需要根据业务场景做出权衡。比如说业务不稳定的阶段,升级频繁,接口版本差异较大,那么使用第一种方式就比较合适。但是,你会发现项目中重复的代码块会越来越多,你还需要关注老接口的生命周期,当完全废弃时,要将这些老接口删除掉,不利于整个项目的管理。第二种方式会比较通用,我们只需要定义好接口的版本并且与客户端约定好版本号,那么每个版本的客户端都会调用相应版本的接口,就不会出现接口不兼容的情况。下面就以第二种方式为例结合 REST 来详细讲解一下怎么定义接口版本。

在 REST API 领域,目前业界比较主流的有 3 种做法来控制版本,第一种做法是在 URI 中直接标记使用哪个版本,无版本号 URI 默认使用最新版本:

http://apigateway/api/v1/student

第二种做法是在每个请求后添加一个 version 参数,表示请求的是哪个版本:

http://apigateway/api/student?version=1

第三种做法是在 HTTP 请求的 header 中添加请求版本信息:

Accept: application/json, text/plain, /

Accept-Encoding: gzip, deflate

Accept-Language: zh-CN,zh;q=0.9

Accept-Version: v2

对比这三种做法,前两种方法的优点是可以很直观地在 URL 中看到版本号,方便调试。缺点是通过 URL 传递了一些业务无关的参数,违反了 REST 规范。第三种方式遵守了 REST 规范,但又不是很直观。至于采用哪种方式,各个公司有不同的实践,我们还是要深入了解这三种做法和它们的优缺点之后,根据实际业务场景和团队现状,做出选择。

总结

设计一套良好的 HTTP API 需要注意 API 风格、单一职责原则、文档管理和版本控制。在面向对象编程领域中,了解并应用这些原则背后的理念,将有助于我们更好地设计业务实现架构,为业务发展打下坚实的基础。通过深入理解这些原则,我们可以在遇到问题以及解决问题的过程中,为业务奠定正确且牢固的基础。