相关的一篇文章:RESTful API URI 设计的一些总结

问题场景:判断一个资源(Resources)是否存在,URI 该如何设计?

应用示例:判断 id 为 1 用户下,名称为 windows 10 的产品是否存在?

如果这个问题出现在 MVC 项目中,我想我们一般会这样设计:

public class ProductService
{
public async Task<bool> IsExist(int userId, string productName)
{
....
}
}

看来没什么问题,的确也没什么问题,那如果把这部分代码搬到 ASP.NET WebAPI 项目中实现,会是怎样呢?我们来看一下:

public class ProductsController : ApiController
{
[HttpGet]
[Route("api/products/isexist/{userId}/{productName}")]
public async Task<bool> IsExist(int userId, string productName)
{
...
}
}

我想你应该发现一些问题了,这种写法完全是 MVC 的方式,但并不适用于 WebAPI,主要有三个问题:

  • Route 定义混乱,完全违背 REST API URI 的一些设计原则。
  • Action 命名不恰当。
  • bool 返回值不合适。

对于上面的三个问题,我们分别来探讨下。

1. URI 设计

首先,我们知道在 REST API 中,URI 代表的是一种资源,它的设计要满足两个基本要求,第一名词而非动词,第二要能清晰表达出资源的含义,换句话说就是,从一个 URI 中,你可以很直接明了的知道访问的资源是什么,我们再来看我们设计的 URI:

api/products/isExist/{userId}/{productName}

这是什么鬼???这种设计完全违背 URI 原则,首先,我们先梳理一下,我们想要请求的资源是什么?没错,是产品(Products),但这个产品是某一个用户下的,所以用户和产品有一个上下级关系,访问产品首先得访问用户,这一点要在 URI 中进行体现,其次,我们是获取产品?还是判断产品是否存在?这个概念是不同的,产品的唯一标识和用户一样,都是 id,在 URI 的一般设计中,如果要访问某一唯一标识下的资源(比如 id 为 1 的 product),会这样进行设计:api/products/{id},HttpClient 请求中会用 HttpGet 方法(api/products/1),这样我们就可以获得一个 id 为 1 的 product,但现在的场景是,获取产品不通过唯一标识,而是通过产品名称,难道我们要这样设计:

api/products/{productName}

咋看之下,这样好像设计也没什么问题,但总觉得有些不对劲,比如如果再加一个产品大小,难道要改成这样:api/products/{productName}/{productSize},这种设计完全是不恰当的,上面说到,URI 代表的是一种资源,通过 URI 获取资源的唯一方式是通过资源的唯一标识,除此之外的获取都可以看作是对资源的查询(Query),所以,针对我们的应用场景,URI 的设计应该是这样(正确):

api/users/{userId}/products: api/users/1/products?productName=windows 10

上面的 URI 清晰明了的含义:查询 id 为 1 用户下名称为 windows 10 的产品。

2. Action 命名

对于 IsExist 的命名,如果没有很强的强迫症,其实也是可以接受的,因为 WebAPI 的 URI 并不会像 MVC 的 Route 设计那样,在访问的时候,URL 一般会默认 Action 的名字,所以,在 WebAPI Action 设计的时候,会在 Action 前面加一个 Route 属性,用来配置 URI,也就是说每一个 Action 操作会对应一个 URI 请求操作,这个请求操作也就是 HTTP 的常用方法。

如果我们想把 IsExist 改掉,那用什么命名会好些呢?先回忆一下,我们在使用 Visual Studio 创建 ASP.NET WebAPI 项目的时候,VS 会自动创建一些示例 Action,我们看看能不能从那里得到一些线索:

public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
} // GET api/values/5
public string Get(int id)
{
return "value";
} // POST api/values
public void Post([FromBody]string value)
{
} // PUT api/values/5
public void Put(int id, [FromBody]string value)
{
} // DELETE api/values/5
public void Delete(int id)
{
}
}

上面是 Values 资源的一些 Action 实现,我们可以看到,Action 的命名和 HTTP 方法一样,比如 Get 就是 Get,而不是 GetById,Get 是动词,表示它对资源的一种操作,具体是通过什么进行操作?在参数中可以很直观的进行反应,一般会在 HelpPage 中进行注释说明。

IsExist 的含义还是判断资源是否存在,其本质上来说就是去获取一个资源,也就是 Get 操作,所以,在 WebAPI Action 中对此的命名,我们直接使用 Get 会好一下,或者使用 Exist。

3. 请求返回

bool 一般是用在项目方法中的返回值,如果用在 HTTP 请求中,就不是很恰当了,我先贴出几篇文章:

上面除去第一篇文章,其他文章都是在讨论:检查一个资源是否存在,REST API 该如何设计(HTTP status code)?客户端获取服务的响应不是通过 bool,而是通过 HTTP 状态码,主要设计三个:404、204 和 200:

  • 404 is correct since the resource "/users/{username}" was Not Found.
  • If the resource exists but it is empty (as in "null") then you should return 204.
  • If the resource exists and contains any data then you should return 200.

204 和 404 有所不同的是,204 表示资源存在,但是为空,404 代表的是原始资源本身就不存在,并且通过唯一标识查询不到,而 204 更多的是表示,在一定条件下的资源不存在,但可以通过唯一标识查询到,所以如果资源不存在返回 204 不恰当,wikipedia 中 200 和 404 详细说明:

  • 200 OK - ... The actual response will depend on the request method used. In a GET request, the response will contain an entity corresponding to the requested resource.
  • 404 Not Found - The requested resource could not be found but may be available again in the future. Subsequent requests by the client are permissible.

HTTP status code 简要说明:

  • 2xx codes Tell a UA that it did the right thing, the request worked. It can keep doing this in the future.(请求成功)
  • 3xx codes Tell a UA what you asked probably used to work, but that thing is now elsewhere. In future the UA might consider just going to the redirect.(请求很可能成功)
  • 4xx codes Tell a UA it did something wrong, the request it constructed isn't proper and shouldn't try it again, without at least some modification.(请求失败)
  • 5xx codes Tell a UA the server is broken somehow. But hey that query could work in the future, so there is no reason not to try it again. (except for 501, which is more of a 400 issue).(请求错误)

在上面文章中,有一段很有意思的对话(请略过翻译):

  • Alien: Computer, please tell me all planets that humans inhabit.(电脑,请告诉我所有适宜人类居住的星球。)

    Computer: 1 result found. Earth(查询到一个结果,地球)
  • Alien: Computer, please tell me about Earth.(电脑,请告诉我地球的信息。)

    Computer: Earth - Mostly Harmless.(地球-无害。)
  • Alien: Computer, please tell me about all planets humans inhabit, outside the asteroid belt.(电脑,请告诉我小行星带以外,所有适宜人类居住的星球。)

    Computer: 0 results found.(没有查询到结果。)
  • Alien: Computer, please destroy Earth.(电话,请毁灭地球。)

    Computer: 200 OK.(毁灭成功。)
  • Alien: Computer, please tell me about Earth.(电脑,请告诉我地球的信息。)

    Computer: 404 - Not Found(没有找到。)
  • Alien: Computer, please tell me all planets that humans inhabit.(电脑,请告诉我所有适宜人类居住的星球。)

    Computer: 0 results found.(没有找到。)
  • Alien: Victory for the mighty Irken Empire!(看不懂啥意思)

搜刮到的一张 HTTP status code 示意图(点击查看大图):

4. 最终代码

针对一开始的应用问题,最终完善代码:

public class ProductsController : ApiController
{
[HttpGet]
[Route("api/users/{userId}/products")]
public async Task<HttpResponseMessage> Get(int userId, string productName)
{
if (true)
{
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
}
} public class WebApiTest
{
[Fact]
public async Task Exists_Product_ByProductName()
{
using (var client = new HttpClient())
{
client.BaseAddress = new System.Uri(Base_Address);
var response = await client.GetAsync("/api/users/1/products?productName=windows 10");
//var requestMessage = new HttpRequestMessage(HttpMethod.Head, "/api/users/1/products?productName=windows 10");
//var response = await client.SendAsync(requestMessage);//更好的调用方式,只请求HEAD。
Console.WriteLine(response.StatusCode);
Assert.True(response.IsSuccessStatusCode);
}
}
}

RESTful API URI 设计: 判断资源是否存在?的更多相关文章

  1. RESTful API URI 设计: 查询(Query)和标识(Identify)

    相关文章:RESTful API URI 设计的一些总结. 问题场景:删除一个资源(Resources),URI 该如何设计? 应用示例:删除名称为 iPhone 6 的产品. 是不是感觉很简单呢?根 ...

  2. RESTful API URI 设计的一些总结

    非常赞的四篇文章: Resource Naming Best Practices for Designing a Pragmatic RESTful API 撰写合格的 REST API JSON 风 ...

  3. 好RESTful API的设计原则

    说在前面,这篇文章是无意中发现的,因为感觉写的很好,所以翻译了一下.由于英文水平有限,难免有出错的地方,请看官理解一下.翻译和校正文章花了我大约2周的业余时间,如有人愿意转载请注明出处,谢谢^_^ P ...

  4. RESTful API的设计原则

    好RESTful API的设计原则   说在前面,这篇文章是无意中发现的,因为感觉写的很好,所以翻译了一下.由于英文水平有限,难免有出错的地方,请看官理解一下.翻译和校正文章花了我大约2周的业余时间, ...

  5. 好的RESTful API的设计原则

    转载自一位大佬 英文原版 Principles of good RESTful API Design Good API design is hard! An API represents a cont ...

  6. [Medium翻译]RESTful API权威设计指南-设计更好的API

    本文为授权译文.希望查看原文的同学请戳链接:https://hackernoon.com/restful-api-design-step-by-step-guide-2f2c9f9fcdbf 对于我们 ...

  7. Restful API的设计与实践

    Restful这个名称应该很多人都不陌生,但是我发现不少人对Restful存在或多或少的理解偏差,其中不泛比较厉害的程序员,所以有必要为Restful来“正名”. Restful是一种软件架构风格,设 ...

  8. Flask一种通用视图,增删改查RESTful API的设计

    模型设计是后端开发的第一步.数据模型反映了各种对象之间的相互关系. from app import db class Role(db.Model): """角色" ...

  9. [转]10个有关RESTful API良好设计的最佳实践

    Web API已经在最近几年变成重要的话题,一个干净的API设计对于后端系统是非常重要的. 通常我们为Web API使用RESTful设计,REST概念分离了API结构和逻辑资源,通过Http方法GE ...

随机推荐

  1. 关于call和apply函数的区别及用法

    call和apply函数是function函数的基本属性,都可以用于更改函数对象和传递参数,是前端工程师常用的函数.具体使用方法请参考以下案列: 例如: 申明函数: var fn = function ...

  2. 优化curl并发使用

    经典curl并发的处理流程:首先将所有的URL压入并发队列, 然后执行并发过程, 等待所有请求接收完之后进行数据的解析等后续处理. 在实际的处理过程中, 受网络传输的影响, 部分URL的内容会优先于其 ...

  3. node、npm、gulp、bower、ionic下载及安装

    node: http://nodejs.cn/ npm: 随node一起安装了 gulp: http://www.gulpjs.com.cn/docs/getting-started/ bower: ...

  4. Django models知识小点

    django 为使用一种新的方式,即关系对象映射(ORM) 一,创建表 1,基本结构 注意: 1,创建标的时候,如果我们不给表加自增列,生成表的时候会默认给我们生成一列为ID的自增列,当然我们也可以自 ...

  5. 工作中那些提高你效率的神器(第二篇)_Listary

    引言 无论是工作还是科研,我们都希望工作既快又好,然而大多数时候却迷失在繁杂的重复劳动中,久久无法摆脱繁杂的事情. 你是不是曾有这样一种想法:如果我有哆啦A梦的口袋,只要拿出神奇道具就可解当下棘手的问 ...

  6. css伪类制作三角箭头

    <meta charset="utf-8"> <style type="text/css"> .tip{ padding: 5px 10 ...

  7. 谢欣伦 - OpenDev原创例程 - 时间同步Time Sync

    很久以前就发现系统自带的时间同步功能很弱,更新时间总是不成功.索性编写一个小软件来更新系统时间,正好用上了我之前写的代码,相关代码可参见文章<化繁为简系列原创教程 - 通信专题 - 无连接套接字 ...

  8. 华为oj 购物单

    这两天断断续续敲完这个(放假的时候比较懒),一次成功有点小激动(●'◡'●)  不过貌似从第一次打开开始计时..... 这道题目很像01背包,我将附件与它们的主件绑定(就是link起来)然后套用动态规 ...

  9. 浅谈C#中常见的委托<Func,Action,Predicate>(转)

    一提到委托,浮现在我们脑海中的大概是听的最多的就是类似C++的函数指针吧,呵呵,至少我的第一个反应是这样的. 关于委托的定义和使用,已经有诸多的人讲解过,并且讲解细致入微,尤其是张子阳的那一篇.我就不 ...

  10. Powershell 十个常见任务

    学习Powershell的时候,基本的语法也了解了一些,但是就是不知道要写些什么?作为一个过来者,和大家一起分享下常见的几个管理任务脚本. 1.更改本地Administrator账号密码 [ADSI] ...