RESTful API URI 设计: 判断资源是否存在?

简介:

如果这个问题出现在 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 示意图(点击查看大图):

211601203965650.png

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);
        }
    }
}


本文转自田园里的蟋蟀博客园博客,原文链接:http://www.cnblogs.com/xishuai/p/designing-rest-api-uri-for-checking-resource-existence.html,如需转载请自行联系原作者
相关文章
|
1月前
|
安全 API 持续交付
要利用阿里云控制API查询您的阿里云资源
【2月更文挑战第33天】要利用阿里云控制API查询您的阿里云资源
20 3
|
11天前
|
安全 Java API
RESTful API设计与实现:Java后台开发指南
【4月更文挑战第15天】本文介绍了如何使用Java开发RESTful API,重点是Spring Boot框架和Spring MVC。遵循无状态、统一接口、资源标识和JSON数据格式的设计原则,通过创建控制器处理HTTP请求,如示例中的用户管理操作。此外,文章还提及数据绑定、验证、异常处理和跨域支持。最后,提出了版本控制、安全性、文档测试以及限流和缓存的最佳实践,以确保API的稳定、安全和高效。
|
14天前
|
小程序 前端开发 API
小程序全栈开发中的RESTful API设计
【4月更文挑战第12天】本文探讨了小程序全栈开发中的RESTful API设计,旨在帮助开发者理解和掌握相关技术。RESTful API基于REST架构风格,利用HTTP协议进行数据交互,遵循URI、客户端-服务器架构、无状态通信、标准HTTP方法和资源表述等原则。在小程序开发中,通过资源建模、设计API接口、定义资源表述及实现接口,实现前后端高效分离,提升开发效率和代码质量。小程序前端利用微信API与后端交互,确保数据流通。掌握这些实践将优化小程序全栈开发。
|
23天前
|
前端开发 Java API
构建RESTful API:Java中的RESTful服务开发
【4月更文挑战第3天】本文介绍了在Java环境中构建RESTful API的重要性及方法。遵循REST原则,利用HTTP方法处理资源,实现CRUD操作。在Java中,常用框架如Spring MVC简化了RESTful服务开发,包括定义资源、设计表示层、实现CRUD、考虑安全性、文档和测试。通过Spring MVC示例展示了创建RESTful服务的步骤,强调了其在现代Web服务开发中的关键角色,有助于提升互操作性和用户体验。
构建RESTful API:Java中的RESTful服务开发
|
27天前
|
XML JSON 安全
谈谈你对RESTful API设计的理解和实践。
RESTful API是基于HTTP协议的接口设计,通过URI标识资源,利用GET、POST、PUT、DELETE等方法操作资源。设计注重无状态、一致性、分层、错误处理、版本控制、文档、安全和测试,确保易用、可扩展和安全。例如,`/users/{id}`用于用户管理,使用JSON或XML交换数据,提升系统互操作性和可维护性。
18 4
|
29天前
|
安全 API 开发者
构建高效可扩展的RESTful API服务
在数字化转型的浪潮中,构建一个高效、可扩展且易于维护的后端API服务是企业竞争力的关键。本文将深入探讨如何利用现代后端技术栈实现RESTful API服务的优化,包括代码结构设计、性能调优、安全性强化以及微服务架构的应用。我们将通过实践案例分析,揭示后端开发的最佳实践,帮助开发者提升系统的响应速度和处理能力,同时确保服务的高可用性和安全。
30 3
|
1月前
|
缓存 前端开发 API
构建高效可扩展的RESTful API:后端开发的最佳实践
【2月更文挑战第30天】 在现代Web应用和服务端架构中,RESTful API已成为连接前端与后端、实现服务间通信的重要接口。本文将探讨构建一个高效且可扩展的RESTful API的关键步骤和最佳实践,包括设计原则、性能优化、安全性考虑以及错误处理机制。通过这些实践,开发者可以确保API的健壮性、易用性和未来的可维护性。
|
1月前
|
API 开发者 UED
深入探讨RESTful API设计原则及最佳实践
在当今互联网时代,RESTful API已成为各种软件系统之间进行通信的重要方式。本文将从资源定义、URI设计、HTTP方法选择、状态码规范等方面深入探讨RESTful API设计的原则与最佳实践,帮助开发者更好地构建高效、健壮的API。
|
17天前
|
缓存 前端开发 API
API接口封装系列
API(Application Programming Interface)接口封装是将系统内部的功能封装成可复用的程序接口并向外部提供,以便其他系统调用和使用这些功能,通过这种方式实现系统之间的通信和协作。下面将介绍API接口封装的一些关键步骤和注意事项。
|
24天前
|
监控 前端开发 JavaScript
实战篇:商品API接口在跨平台销售中的有效运用与案例解析
随着电子商务的蓬勃发展,企业为了扩大市场覆盖面,经常需要在多个在线平台上展示和销售产品。然而,手工管理多个平台的库存、价格、商品描述等信息既耗时又容易出错。商品API接口在这一背景下显得尤为重要,它能够帮助企业在不同的销售平台之间实现商品信息的高效同步和管理。本文将通过具体的淘宝API接口使用案例,展示如何在跨平台销售中有效利用商品API接口,以及如何通过代码实现数据的统一管理。