name: api-design description: 遵循最佳实践设计RESTful API、GraphQL模式和RPC接口,以确保一致性、可用性和可扩展性。触发关键词:api设计,REST,RESTful,REST API,GraphQL,GraphQL模式,gRPC,OpenAPI,Swagger,端点,路由,资源,CRUD,HTTP方法,GET,POST,PUT,PATCH,DELETE,状态码,分页,过滤,排序,版本控制,HATEOAS,API版本控制,模式设计,RPC,服务设计。 allowed-tools: Read, Grep, Glob, Edit, Write
API设计
概述
本技能指导设计结构良好、直观、一致且可扩展的API。它涵盖REST、GraphQL和gRPC模式,重点在于开发者体验和长期可维护性。
指令
1. 理解需求
- 识别API消费者和用例
- 定义资源模型和关系
- 确定认证/授权需求
- 规划版本控制策略
2. 设计资源结构
- 定义清晰的资源层次结构
- 建立命名约定
- 规划URL模式
- 设计请求/响应模式
3. 定义操作
- 将CRUD操作映射到HTTP方法
- 设计查询参数进行过滤/排序
- 规划分页方法
- 定义错误响应格式
4. 文档化API
- 创建OpenAPI/Swagger规范
- 包含所有端点的示例
- 文档化认证流程
- 提供SDK使用示例
最佳实践
RESTful API设计
- 使用名词作为资源:
/users,而不是/getUsers - 一致的命名:在所有端点中一致使用snake_case或camelCase
- 正确的HTTP方法:
- GET(读取/获取),POST(创建),PUT(完全更新),PATCH(部分更新),DELETE(删除)
- 有意义的状态码:
- 200(OK),201(Created),204(No Content)
- 400(Bad Request),401(Unauthorized),403(Forbidden),404(Not Found)
- 409(Conflict),422(Unprocessable Entity)
- 500(Internal Server Error),503(Service Unavailable)
- 幂等性:PUT和DELETE应该是幂等的;考虑为POST使用幂等键
- HATEOAS:在响应中包含相关资源的链接
- 过滤与排序:使用查询参数(
?status=active&sort=-created_at) - 分页:大数据集使用基于游标的分页,简单情况使用偏移分页
GraphQL模式设计
- 使用描述性类型:清晰、自文档化的类型和字段名
- 连接模式:使用relay-style连接进行分页列表
- 输入对象:为变体分离输入类型(CreateUserInput,UpdateUserInput)
- 错误处理:将错误作为负载的一部分返回,而不仅仅是顶级异常
- 可空性:明确可空与非可空字段
- 变体返回负载:在变体响应中包括数据和错误
- 避免过度获取:设计片段和字段以匹配常见查询模式
gRPC服务设计
- 服务命名:为RPC方法使用动词(CreateUser,GetUser,ListUsers)
- 消息重用:为常见模式定义共享消息类型
- 分页:使用page_token模式进行基于游标的分页
- 错误:使用google.rpc.Status提供丰富的错误详情
- 版本控制:使用包版本控制(myapi.v1,myapi.v2)
- 流式传输:在适当情况下利用服务器/客户端/双向流式传输
API版本控制策略
- URL路径版本控制:
/v1/users,/v2/users(显式、简单、可缓存) - 头部版本控制:
API-Version: 2或Accept: application/vnd.api.v2+json(URL更简洁) - 查询参数版本控制:
?version=2(备用选项,不太推荐) - 语义版本控制:主版本用于破坏性更改,次版本用于功能,补丁用于修复
- 弃用政策:宣布日落日期,至少支持N-1个版本
示例
示例1:RESTful资源设计
# OpenAPI 3.0 规范
openapi: 3.0.0
info:
title: 电子商务API
version: 1.0.0
paths:
/products:
get:
summary: 列出所有产品
parameters:
- name: category
in: query
schema:
type: string
- name: min_price
in: query
schema:
type: number
- name: page
in: query
schema:
type: integer
default: 1
- name: per_page
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
"200":
description: 分页的产品列表
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/Product"
pagination:
$ref: "#/components/schemas/Pagination"
post:
summary: 创建新产品
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ProductInput"
responses:
"201":
description: 产品已创建
"400":
description: 验证错误
/products/{id}:
get:
summary: 获取特定产品
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: 产品详情
"404":
description: 产品未找到
示例2:GraphQL模式设计
type Query {
"""
通过ID获取用户
"""
user(id: ID!): User
"""
列出用户,可选过滤
"""
users(filter: UserFilter, first: Int = 20, after: String): UserConnection!
}
type Mutation {
"""
创建新用户账户
"""
createUser(input: CreateUserInput!): CreateUserPayload!
"""
更新现有用户
"""
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
}
type User {
id: ID!
email: String!
name: String!
createdAt: DateTime!
orders(first: Int, after: String): OrderConnection!
}
input CreateUserInput {
email: String!
name: String!
password: String!
}
type CreateUserPayload {
user: User
errors: [UserError!]!
}
type UserError {
field: String
message: String!
code: UserErrorCode!
}
示例3:gRPC服务定义
syntax = "proto3";
package ecommerce.v1;
import "google/protobuf/timestamp.proto";
service ProductService {
// 通过ID获取单个产品
rpc GetProduct(GetProductRequest) returns (GetProductResponse);
// 列出产品,带过滤和分页
rpc ListProducts(ListProductsRequest) returns (ListProductsResponse);
// 创建新产品
rpc CreateProduct(CreateProductRequest) returns (CreateProductResponse);
// 更新现有产品
rpc UpdateProduct(UpdateProductRequest) returns (UpdateProductResponse);
// 删除产品
rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse);
}
message Product {
string id = 1;
string name = 2;
string description = 3;
int64 price_cents = 4;
string category = 5;
google.protobuf.Timestamp created_at = 6;
google.protobuf.Timestamp updated_at = 7;
}
message ListProductsRequest {
// 可选类别过滤
string category = 1;
// 最小价格过滤(单位:分)
optional int64 min_price_cents = 2;
// 返回的最大项目数
int32 page_size = 3;
// 来自先前响应的页面令牌
string page_token = 4;
}
message ListProductsResponse {
repeated Product products = 1;
string next_page_token = 2;
int32 total_count = 3;
}
message CreateProductRequest {
string name = 1;
string description = 2;
int64 price_cents = 3;
string category = 4;
}
message CreateProductResponse {
Product product = 1;
}
示例4:错误响应设计(REST)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求验证失败",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "邮箱必须是有效的邮箱地址"
},
{
"field": "age",
"code": "OUT_OF_RANGE",
"message": "年龄必须在18到120岁之间"
}
],
"request_id": "req_abc123xyz",
"documentation_url": "https://api.example.com/docs/errors#VALIDATION_ERROR"
}
}
示例5:使用头部进行API版本控制
# 带API版本头部的请求
GET /users/123 HTTP/1.1
Host: api.example.com
Accept: application/vnd.api.v2+json
Authorization: Bearer eyJ...
# 响应包含弃用警告
HTTP/1.1 200 OK
Content-Type: application/vnd.api.v2+json
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Deprecation: true
Link: <https://api.example.com/v3/users/123>; rel="successor-version"
{
"id": "123",
"email": "user@example.com",
"name": "John Doe"
}