所有文章 > 日积月累 > 微服务架构中的 API 设计
微服务架构中的 API 设计

微服务架构中的 API 设计

微服务架构是一种将单个应用程序拆分为多个小服务的软件架构风格,每个服务负责一个特定的业务功能,可以独立部署、扩展和维护。在微服务架构中,API 设计是关键组成部分,它决定了服务之间如何交互和协作,对系统的性能、可靠性和可维护性至关重要。本文将详细介绍微服务架构中的 API 设计原则、最佳实践和具体规范。

一、API 设计的核心概念

在微服务架构中,API 设计涉及以下几个核心概念:

  • 服务:微服务架构中的基本组件,负责一个特定的业务功能。
  • API:服务之间通信的主要方式,定义了服务如何交互和协作。
  • 协议:API 通信的规则,如 HTTP、gRPC 等。
  • 数据格式:API 传输的数据结构,如 JSON、XML 等。
  • 版本控制:API 的发展版本,以适应不断变化的业务需求和技术环境。

二、API 设计的原则

1. RESTful 原则

RESTful API 是微服务架构中常用的 API 设计风格,它具有简洁、可读性强的特点。设计 RESTful API 时,应遵循以下原则:

  • 资源导向:API 应该围绕资源设计,每个资源应有唯一的 URI。例如,用户资源可以设计为 /users,特定用户可以通过 /users/{userId} 访问。
  • 使用 HTTP 方法:利用 HTTP 的方法来定义操作类型,如 GET 用于查询、POST 用于创建、PUT 用于更新、DELETE 用于删除。
  • 无状态性:每个请求应包含所有必要的信息,以便服务器能够理解请求而无需保留客户端的状态。

2. 版本控制

随着 API 的不断演进,版本控制是必不可少的。推荐使用 URL 路径中的版本号,例如 /v1/users。这样可以确保在发布新版本时不会影响现有客户端,同时也使得不同版本的 API 可以并存,方便过渡和测试。

3. 安全性

在微服务环境中,安全性尤为重要。常见的身份认证方式包括:

  • OAuth 2.0:允许第三方应用访问用户资源,而无需分享用户的登录凭证。
  • JWT(JSON Web Tokens):用于安全地传输用户信息,通常与 OAuth 2.0 结合使用。

4. 错误处理

统一的错误处理机制可以提高 API 的用户体验。应使用标准化的 HTTP 状态码来表示不同类型的错误,例如:

  • 400 Bad Request:请求无效或缺少必需的参数。
  • 401 Unauthorized:认证失败或缺少认证信息。
  • 404 Not Found:请求的资源不存在。
  • 500 Internal Server Error:服务器内部错误。

三、API 设计的最佳实践

1. 定义清晰的 API 合约

API 合约定义了服务提供者和消费者之间的明确协议,包括请求格式、响应格式、错误处理等。这有助于确保 API 的一致性和稳定性,同时也促进了团队内部和团队之间的有效沟通。

  • 使用 OpenAPI 规范(原 Swagger):通过这一开放源代码框架,可以设计、构建、记录和使用 RESTful API。OpenAPI 可以帮助自动生成代码和 API 文档,从而加速开发过程。
  • 错误处理:明确定义如何返回错误信息。通常应该包括错误代码和对用户有帮助的错误消息,以便开发者更容易地调试和处理异常情况。

2. 利用 API 网关

API 网关是微服务架构中的一个重要组件,它处于客户端和服务之间,提供了路由请求、认证、监控和缓存等功能。

  • 路由请求:API 网关将外部请求路由到适当的微服务,有助于简化客户端逻辑,因为客户端不需要知道服务的位置和其他细节。
  • 提高安全性:通过在网关层实现身份验证和授权机制,可以防止未授权的访问,从而提高整体的系统安全性。

3. 采用版本管理

在微服务架构中,独立地更新和部署服务是常见的需求。因此,对 API 进行有效的版本管理至关重要。

  • URL 版本控制:通过在 URL 中加入版本号(例如 /v1//v2/)可以帮助客户端选择合适的 API 版本。
  • 避免破坏性更新:当进行必要的改动可能影响到现有客户端时,应该通过发布一个新的版本号来管理这些变更,从而尽量减少对客户的影响。

4. 强化安全措施

在设计微服务中的 API 时,强化安全措施是不可或缺的环节,以确保数据的安全和隐私。

  • 采用 HTTPS:通过使用 HTTPS 来加密客户端和服务器之间的通信,可以保护数据不被窃取或篡改。
  • OAuth 2.0:这是一种授权框架,允许第三方应用通过 HTTP 服务获得有限的访问权限,而无需直接处理用户的登录信息。

5. 采用合适的数据格式

选择合适的数据传输格式对于 API 的性能和可用性有重大影响。

  • JSON(JavaScript Object Notation):这是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。JSON 已成为 Web API 的标准格式。
  • Protocol Buffers:由 Google 开发,是一种更高效、更小的二进制格式,适用于性能敏感的环境和跨语言的服务通信。

四、API 设计的具体规范

1. 命名规范

如无特别说明,路径、参数、变量等命名统一采用首字母小写的驼峰方式。

2. 字符编码

如无特别说明,字符编码统一采用 UTF-8。

3. URL 规范

请求方式

  • GET 读取
  • POST 创建,非幂等
  • PUT 整体替换,幂等
  • PATCH 部分更新
  • DELETE 删除

为了简化沟通和理解,如非必要请勿使用 PUT、PATCH 和 DELETE,这三种请求方式均可使用 POST 结合路径中的操作来实现。

路径和参数

  • 围绕资源来操作
GET /user/list 列表查询
GET /user/info/1 ID 查询,如需查询多个,ID 列表通过 Query 参数传递,形如 ids=1,2,3
POST /user/create 创建
POST /user/update/1 更新,只更新传递的字段,未传递的字段保持不变
POST /user/replace/1 替换,整体更新,未传递的字段将清空
POST /user/delete/1 删除,如需删除多个,ID 列表通过请求体传递,形如 {"ids": [1, 2, 3]}
  • 非资源 ID 参数放到 Query 里
GET /user/list?gender=male&minAge=18
GET /user/infoByUsername?username=jack
GET /user/follow?followingId=1&followerId=2 涉及到多个 ID 组合才能定位资源时,统一通过 Query 参数传递
  • 业务操作放到路径里
POST /user/sendVerifyCode

4. API 路径

对于采用微服务架构的应用,需要通过路径前缀来区分前后端资源,以及不同的微服务。

  • 所有微服务的 API 统一到 /api 路径前缀下,可在反向代理层添加该前缀,在传递给后端服务时抹去该前缀,以便该前缀对后端服务来说无感知。
  • 在应用网关层为各微服务添加相应的路径前缀(比如用户微服务 /user),该前缀同样对各微服务来说无感知。
  • 在微服务内部按模块划分 API,比如用户微服务的用户模块的创建用户 API /user/create(对外暴露的完整路径为 /api/user/user/create,这里服务名和模块名同名)。

5. API 版本

对于内部使用的 API,自己可以控制客户端升级节奏,应避免使用 API 版本,因为维护多版本 API 的成本很高。对于对外开放的 API,由于无法控制使用方客户端升级节奏,那么可以通过多版本 API 来实现平滑升级,在废弃老版 API 之前给使用方留足够的升级时间。

API 版本号可在不同的层级上添加,以前面的创建用户 API /api/user/user/create 为例,按作用范围由大到小有以下几种方式:

  • /api/v1/user/user/create 在应用网关层级添加(推荐)。
  • /api/user/v1/user/create 在微服务层级添加。
  • /api/user/user/v1/create 在模块层级添加。
  • /api/user/user/create/v1 在 API 层级添加。

不建议为不同版本的服务启动不同的实例,随着版本的不断升级,后期的维护工作会越来越大。对于同一个服务的不同版本 API,应使用一套代码,新版 API Controller 可以通过继承老版 Controller 来尽量复用现有代码。

6. 请求

公共参数

通过 HTTP 头来传递公共参数,优先使用 HTTP 标准头,没有合适的再自定义,自定义 HTTP 头需以 X- 打头。

HTTP 头示例用途
AuthorizationBearer认证服务颁发的 Token
Accept-Languagezh-CN,zh;q=0.9,en-US,en;q=0.1可接受的响应内容语言,优先使用权重高的
X-Client-NameBasicAI客户端名称
X-Client-Versionv1.0.0客户端版本

请求体

  • 请求体采用 JSON 格式,且为一个 JSON 对象。
  • 请求体为单个值时需包装为一个对象,以保持最外层结构统一。

示例

{
"username": "jack",
"password": "12345678",
"age": 17
}

7. 响应

响应状态码

HTTP 状态码设计的初衷是用于静态资源访问场景,对于 API 这种动态服务场景,许多状态码都不适用,或者根本无法表示各种千奇百怪的业务错误。因此这里推荐只使用下面这些少量的 HTTP 状态码,其它业务异常情况统一响应 200 状态码,并在响应体里通过 code 返回具体的业务错误码。

  • 200 OK 操作成功
  • 400 Bad Request 一般性客户端错误
  • 401 Unauthorized 未认证
  • 403 Forbidden 未授权
  • 404 Not Found 资源未找到
  • 405 Method Not Allowed 请求方式不支持
  • 429 Too Many Requests 请求太频繁
  • 500 Internal Server Error 服务器内部错误
  • 502 Bad Gateway 网关请求上游服务时出错
  • 503 Service Unavailable 服务不可用,比如重启或维护中
  • 504 Gateway Timeout 网关请求上游服务超时

响应体

响应体为 JSON 对象,结构如下:

{
"code": "OK", // 业务错误码
"message": "", // 业务错误描述
"data": null // 业务数据
}
  • 业务错误码采用常量字符串格式(只能使用大写字母、下划线和数字),形如 USER__USER__USERNAME_DUPLICATED,其中前两级依次为服务和模块,一些与服务和模块无关的公共错误码没有前缀。
  • 业务错误描述可直接展示给用户(但不推荐),请勿包含任何涉密信息,考虑到国际化,请使用英文。
  • 业务数据如果只有单项那么直接通过 data 字段返回,这样可以让后端省去大量的包装类定义,如果有多项那么只能再包一层,在 data 下通过不同字段区分,如果没有则为 null
  • 只有在 HTTP 响应状态码为 200 时才保证响应体符合标准格式。

单项业务数据直接通过 data 字段返回示例

{
"code": "OK",
"message": "",
"data": {
"id": 1,
"username": "jack"
}
}

多项业务数据需包一层示例

{
"code": "OK",
"message": "",
"data": {
"user": {
"id": 1,
"username": "jack"
},
"roles": []
}
}

列表数据示例

{
"code": "OK",
"message": "",
"data": {
"list": [],
"total": 1000
}
}

业务出错示例

{
"code": "BILLING__PAY__MONEY_NOT_ENOUGH",
"message": "money not enough",
"data": null
}

8. 分页规范

基于页

适合客户端有翻页条,按页展示数据,数据集变化较慢的场景。

  • 分别使用 totalpageNopageSizelist 来传递和返回总数(可选)、当前页码、每页条数和当页数据。
  • 冗余返回当前页码和每页条数,以便客户端依据响应结果即可知道如何请求下页数据。
{
"code": "ok",
"message": "",
"data": {
"total": 100,
"pageNo": 1,
"pageSize": 10,
"list": []
}
}

基于偏移量

适合没有翻页条的流式加载模式场景,如果起始位置使用主键 ID、创建时间这样的排序字段,服务端可以通过大于或等于比较来加速查询,并且在数据集有变化时能够保证分批获取的数据不重复。

  • 分别使用 totaloffsetlimitlist 来传递和返回总数(可选)、起始位置、返回条数和当页数据。
  • 起始位置除了是位置序号,还可以是任意有序字段的值,比如创建时间。
  • 冗余返回起始位置和返回条数,以便客户端依据响应结果即可知道如何请求下页数据。
{
"code": "ok",
"message": "",
"data": {
"total": 100,
"offset": 0,
"limit": 10,
"list": []
}
}

五、案例分析

假设我们正在设计一个电商平台的微服务架构,其中包括用户服务、订单服务和支付服务。每个服务都有自己的 API,这些 API 通过 RESTful 设计原则进行设计,并实现了 OAuth 2.0 身份认证。我们为每个服务提供了详细的 Swagger 文档,并在持续集成过程中运行自动化测试,以确保各个服务间的交互顺畅。

用户服务

  • 创建用户
POST /api/user/user/create
Content-Type: application/json

{
"username": "jack",
"password": "12345678",
"age": 17
}
  • 获取用户信息
GET /api/user/user/info/1

订单服务

  • 创建订单
POST /api/order/order/create
Content-Type: application/json

{
"userId": 1,
"items": [
{
"itemId": 101,
"quantity": 2
},
{
"itemId": 102,
"quantity": 1
}
]
}
  • 获取订单列表
GET /api/order/order/list?userId=1

支付服务

  • 创建支付
POST /api/payment/payment/create
Content-Type: application/json

{
"orderId": 1,
"amount": 100
}
  • 获取支付状态
GET /api/payment/payment/status/1

六、总结

微服务架构下的 API 设计需要综合考虑资源管理、版本控制、安全性、错误处理以及文档和测试等多个方面。通过遵循这些最佳实践,我们可以构建出高效、可靠的 API,从而支持系统的可扩展性和灵活性。希望本文对您有所帮助,并为您的微服务架构设计提供了一些有价值的信息和见解。

#你可能也喜欢这些API文章!