RESTful API URI 设计的一些总结

RESTful API URI 设计的一些总结

非常赞的四篇文章:

本篇阅读目录:

  • 1. HTTP Methods
  • 2. JSON API 命名规则
  • 3. API URI design
  • 4. WebAPI 使用 snake_case 还是 camelCasing?

1. HTTP Methods

HTTP 常用方法:

  • GET: 获取某个资源。
  • POST: 创建一个新的资源。
  • PUT: 替换某个已有的资源。
  • PATCH: 修改某个已有的资源。
  • DELETE:删除某个资源。

我原先以为修改某一个资源,也是用 POST,后来发现还有一个 PATCH,但发现 HttpClient 并没有提供此调用方法,需要我们进行扩展:

public static class HttpClientExtensions
{
public static async Task<HttpResponseMessage> PatchAsync(this HttpClient client, Uri requestUri, HttpContent iContent)
{
var method = new HttpMethod("PATCH");
var request = new HttpRequestMessage(method, requestUri)
{
Content = iContent
}; HttpResponseMessage response = new HttpResponseMessage();
try
{
response = await client.SendAsync(request);
}
catch (TaskCanceledException e)
{
Debug.WriteLine("ERROR: " + e.ToString());
} return response;
}
}

调用代码:

HttpContent httpContent = new StringContent("Your JSON-String", Encoding.UTF8, "application/json");
var responseMessage = await httpClient.PatchAsync(new Uri("testUri"), httpContent);

2. JSON API 命名规则

相关阅读:You should use camelCase with JSON, but snake_case is 20% easier to read

camelCase(骆驼命名)我们都非常熟悉,因为 C# 就是使用的这个命名法,snake_case(蛇形命名)适用于 python 和 ruby,比如商品 ID,camelCase 会命名为 productId,snake_case 则会命名为 product_id。

需要注意的是,snake_case 只限于 JSON API 命名,并不限于 URI,URI 中一般也不会使用下划线,为什么要对 JSON API 进行规范命名?因为 RESTful 是无状态风格,也就是说 RESTful API 并不限于某一种客户端进行调用,所以 JSON API 的命名必须要规范,如果只是 C# 调用的话,那么命名采用 camelCase 命名就可以了,但显然并不是这样,最后得出的结论是使用 snake_case 命名会比较好,以后在设计的时候,需要注意了。

3. API URI design

API URI 设计最重要的一个原则:nouns (not verbs!),名词(而不是动词)。

CRUD 简单 URI:

  • GET /users - 获取用户列表
  • GET /users/1 - 获取 Id 为 1 的用户
  • POST /users - 创建一个用户
  • PUT /users/1 - 替换 Id 为 1 的用户
  • PATCH /users/1 - 修改 Id 为 1 的用户
  • DELETE /users/1 - 删除 Id 为 1 的用户

上面是对某一种资源进行操作的 URI,那如果是有关联的资源,或者称为级联的资源,该如何设计 URI 呢?比如某一用户下的产品:

  • GET /users/1/products - 获取 Id 为 1 用户下的产品列表
  • GET /users/1/products/2 - 获取 Id 为 1 用户下 Id 为 2 的产品
  • POST /users/1/products - 在 Id 为 1 用户下,创建一个产品
  • PUT /users/1/products/2 - 在 Id 为 1 用户下,替换 Id 为 2 的产品
  • PATCH /users/1/products/2 - 修改 Id 为 1 的用户下 Id 为 2 的产品
  • DELETE /users/1/products/2 - 删除 Id 为 1 的用户下 Id 为 2 的产品

还有一种情况,我们一般在设计 API 的时候,会进行一些查询操作,比如分页和排序等,API 方法参数设计可能很容易,那重要的 URI 该如何设计呢?我们先看这样的一个设计:

[HttpGet]
[Route("api/wzlinks/users-{spaceUserId}/{pageIndex=1}/{pageSize=20}")]
public async Task<IEnumerable<WzLinkDTO>> GetPagedList(int spaceUserId, int pageIndex, int pageSize)
{
.....
}

首先,这个 URI 想要表示的意思是:获取某一用户下,分页查询的网摘列表,这个 API 设计好不好呢?我们看下 GitHub 中的一个 API:

"current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}"

差别是不是很大?而且我们设计的 URI 表达也比较混乱,查询应该是参数,并且是对 URI 进行的查询,所以放在 URI 中会不太合适,我们完善下:

[HttpGet]
[Route("api/users/{space_user_id}/wzlinks")]
public async Task<IEnumerable<WzLinkDTO>> GetPagedList(int space_user_id, int page, int per_page)
{
.....
}

URI 表达为:获取 space_user_id 为 1 用户下的网摘分页列表,上面设计会不会更好些呢?调用示例:

api.cnblogs.com/api/users/1/wzlinks?page=1&per_page=20

GitHub API(规范参考):https://api.github.com

{
"current_user_url": "https://api.github.com/user",
"current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
"authorizations_url": "https://api.github.com/authorizations",
"code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}",
"emails_url": "https://api.github.com/user/emails",
"emojis_url": "https://api.github.com/emojis",
"events_url": "https://api.github.com/events",
"feeds_url": "https://api.github.com/feeds",
"following_url": "https://api.github.com/user/following{/target}",
"gists_url": "https://api.github.com/gists{/gist_id}",
"hub_url": "https://api.github.com/hub",
"issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}",
"issues_url": "https://api.github.com/issues",
"keys_url": "https://api.github.com/user/keys",
"notifications_url": "https://api.github.com/notifications",
"organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}",
"organization_url": "https://api.github.com/orgs/{org}",
"public_gists_url": "https://api.github.com/gists/public",
"rate_limit_url": "https://api.github.com/rate_limit",
"repository_url": "https://api.github.com/repos/{owner}/{repo}",
"repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}",
"current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}",
"starred_url": "https://api.github.com/user/starred{/owner}{/repo}",
"starred_gists_url": "https://api.github.com/gists/starred",
"team_url": "https://api.github.com/teams",
"user_url": "https://api.github.com/users/{user}",
"user_organizations_url": "https://api.github.com/user/orgs",
"user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}",
"user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
}

4. WebAPI 使用 snake_case 还是 camelCasing?

补充:因为 space_user_id 违反 C# 的命名规则,Google 搜索“asp net web api snake_case”,却搜多到大量的“camelCasing”关键字,而且在微软大部分 WebAPI 示例中,Route 的参数命名设计规范都是 camelCasing,所以。。。。没办法,只能使用 camelCasing 命名规则吧,谁让用的是 .NET 呢,不过,有人还搞了个 SnakeCaseFormUrlEncodedMediaTypeFormatter 扩展,但好像是在过程中进行了转化,并不是解决定义问题。

网摘的 API 我们再修改下:

[HttpGet]
[Route("api/users/{spaceUserId}/wzlinks")]
public async Task<IEnumerable<WzLinkDTO>> GetPagedList(int spaceUserId, int page, int perPage)
{
.....
}

不经意间,还发现 ASP.NET WebAPI Help 一个有意思的地方,比如上面的 API 设计,得到的是这样的 Help 说明:

RESTful API URI 设计的一些总结

如果我们把 API 代码修改成:

[HttpGet]
[Route("api/users/{space_user_id}/wzlinks")]
public async Task<IEnumerable<WzLinkDTO>> GetPagedList(int spaceUserId, int page, int perPage)
{
.....
}

得到的却是这样的 Help 说明:

RESTful API URI 设计的一些总结

发现有什么不同了吗?看来 ASP.NET WebAPI Help 还是蛮智能的呢。

上一篇:django-表单之新增字段和设置css属性(四)


下一篇:简单谈谈Hilt——依赖注入框架