到底什么样的 REST 才是最佳 REST?

news2025/1/20 16:30:41

说起 REST API,小伙伴们多多少少都有听说过,但是如果让你详细介绍一下什么是 REST,估计会有很多人讲不出来,或者只讲出来其中一部分。

今天松哥就来和大家一起来聊一聊到底什么是 REST,顺便再来看下 Spring HATEOAS 的用法。

1. REST 成熟模型

首先关于 REST,有一个大佬 Leonard Richardson 为 REST 定义了一个成熟度模型,他一共定义了四个不同的层次,分别如下:

  1. Level0:Web 服务单纯的使用 HTTP 作为数据传输方式,本质上就是远程方法调用,常见的 SOAP 和 RPC 基本上都属于这一类。
  2. Level1:在这一级别上,引入了资源的概念,服务端的每一个资源,都有一个对应的操作地址。
  3. Level2:在这一级别上,我们引入了不同的 HTTP 请求方法来描述不同的操作,例如 GET 表示查询、POST 表示插入、PUT 表示更新、DELETE 表示删除,并且使用 HTTP 的状态码来表示不同的响应结果。一般来说,大家在日常的接口开发中,基本上都能做到这一层级。但是这还不是最佳结果。
  4. Level3:按照 Leonard Richardson 的意思,这一层级的 REST 基于 HATEOAS(Hypertext As The Engine Of Application State),在这一级别上,除了返回资源的 JSON 之外,还会额外返回一组 Link,这组 Link 描述了对于该资源可以做哪些操作,以及具体的该怎么做。

在日常的开发中,我们一般都是只实现到 Level2 这一层级,真正做到 Level3 的估计很少,不过虽然在工作中一般不会做到 Level3 这一层级,但是,我相信很多小伙伴应该是见过 Level3 层级的 REST 是啥样子的,特别是看过 vhr 视频的小伙伴,松哥在其中讲过,通过 Spring Data Jpa+Spring Rest Repositories 实现的 CURD 接口,其实就是一个达到了 Level3 层级的 REST。

2. Spring HATEOAS

那么接下来我先用 Spring HATEOAS 写一个简单的 REST,然后结合这个案例来和小伙伴们聊一聊到底 Spring HATEOAS 有何不一样的地方。

首先我们创建一个 Spring Boot 工程,引入 Web 和 Spring HATEOAS 依赖,如下:

创建好之后,我们首先创建一个 User 实体类:

public class User extends RepresentationModel {
    private Integer id;
    private String username;
    private String address;
    //省略 getter/setter
}

注意这个 User 实体类需要继承自 RepresentationModel,以方便后续添加不同的 Link(以前旧的版本需要继承自 ResourceSupport)。

接下来写一个简单的测试接口。

查询所有用户:

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping
    public CollectionModel<User> list() {
        List<User> list = new ArrayList<>();
        User u1 = new User();
        u1.setId(1);
        u1.setUsername("javaboy");
        u1.setAddress("www.javaboy.org");
        u1.add(WebMvcLinkBuilder.linkTo(UserController.class).slash(u1.getId()).withSelfRel());
        list.add(u1);
        User u2 = new User();
        u2.setId(2);
        u2.setUsername("itboy");
        u2.setAddress("www.itboyhub.com");
        u2.add(WebMvcLinkBuilder.linkTo(UserController.class).slash(u2.getId()).withSelfRel());
        list.add(u2);
        CollectionModel<User> users = CollectionModel.of(list);
        users.add(WebMvcLinkBuilder.linkTo(UserController.class).withRel("users"));
        return users;
    }
}

关于这个接口,我来说几点:

  1. 首先,对于这种返回一个集合或者数组的情况,返回的类型都是 CollectionModel。
  2. 把集合弄好之后(正常应该去数据库中查询,我这里省事直接创建了),通过 CollectionModel.of(list) 方法去获取一个 CollectionModel<User> 对象。
  3. 对于每一个 user 对象,我都添加了一个 Link 对象,WebMvcLinkBuilder.linkTo(UserController.class).slash(u1.getId()).withSelfRel() 表示生成当前对象的访问链接。
  4. WebMvcLinkBuilder.linkTo(UserController.class).withRel("users") 表示访问所有数据的链接。

好了,这个接口写完之后,我们访问看下:

可以看到,返回的每一个 user 对象中,都有一个链接表示如何单独访问这个对象。最下面还有一个访问所有对象的链接。

对于上面这个案例,可能有小伙伴会质疑,难道我们从数据库中查询出来的 List 集合都要遍历一遍,然后给每一个 User 添加一个 Link 吗?其实不必,添加 Link 这个事可以直接在 User 类中完成,如下:

public class User extends RepresentationModel {
    private Integer id;
    private String username;
    private String address;

    public User(Integer id) {
        super(WebMvcLinkBuilder.linkTo(UserController.class).slash(id).withSelfRel());
        this.id = id;
    }
    //省略 getter/setter
}

可以看到,直接在构造方法中完成即可。此时接口里就不用那么复杂了,如下:

@GetMapping
public CollectionModel<User> list() {
    List<User> list = new ArrayList<>();
    User u1 = new User(1);
    u1.setUsername("javaboy");
    u1.setAddress("www.javaboy.org");
    list.add(u1);
    User u2 = new User(2);
    u2.setUsername("itboy");
    u2.setAddress("www.itboyhub.com");
    list.add(u2);
    CollectionModel<User> users = CollectionModel.of(list);
    users.add(WebMvcLinkBuilder.linkTo(UserController.class).withRel("users"));
    return users;
}

那么对于根据 ID 来查询用户的需求,我们也应该给一个接口如下:

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public EntityModel<User> getOne(@PathVariable Integer id) throws NoSuchMethodException {
        User u = new User(id);
        u.setUsername("javaboy");
        u.setAddress("深圳");
        u.add(Link.of("http://localhost:8080/users/"+id, "getOne"));
        Link users = WebMvcLinkBuilder.linkTo(UserController.class).withRel("users");
        u.add(users);
        Link link = WebMvcLinkBuilder.linkTo(UserController.class).slash(u.getId()).withSelfRel();
        u.add(link);
        Method method = UserController.class.getMethod("getOne", Integer.class);
        Link link2 = WebMvcLinkBuilder.linkTo(method, id).withSelfRel();
        u.add(link2);
        return EntityModel.of(u);
    }
}

关于这个接口,我说如下几点:

  1. 如果返回类型是一个对象的话,需要使用 EntityModel<User> 类型。
  2. 搞好返回的对象之后,通过 EntityModel.of(u) 方法可以获取到目标数据类型。
  3. 这个地方,为了给小伙伴们演示不同的 Link 添加方式,我写了好多个(单纯为了演示不同的 Link 添加方式):
    1. Link.of("http://localhost:8080/users/"+id, "getOne") 这种是自己纯手工去生成当前对象的访问链接,很明显这不是一个很好的方案。当前对象的访问链接建议使用上文中提到的方式。
    2. WebMvcLinkBuilder.linkTo(UserController.class).withRel("users") 这个是生成当前这个 Controller 的访问链接,一般就是访问所有用户对象的链接。
    3. WebMvcLinkBuilder.linkTo(UserController.class).slash(u.getId()).withSelfRel() 前文已经用过了,不多说了,实际应用中建议使用这种。
    4. 也可以根据某一个方法自动生成,像这样 WebMvcLinkBuilder.linkTo(method, id).withSelfRel(),这个是生成某一个具体方法的访问链接。

好了,现在我们来看下这个接口生成的 JSON,如下:

生成的这段 JSON 我将之标记为了三部分:

  1. 第一部分,self,就是自身的访问链接,这三个链接分别是 User 的构造方法,以及前面提到的 3.3 和 3.4 的方法生成的。
  2. 第二部分,getOne 这个,是前面 3.1 中提到的方法生成的。
  3. 第三部分,users 这个,是前面提到的 3.2 方法生成的。

当然,其实这块还有很多其他的生成链接的玩法,但是我就不一一介绍了,小伙伴们可以参考官方文档:

  • https://docs.spring.io/spring-hateoas/docs/current/reference/html

从上面 Spring HATEOAS 中返回的 JSON 我们大致上可以看到它的特点:

当我们使用了 Spring HATEOAS,此时,客户端就会通过服务端返回的 Link Rel 来获取请求的 URI(如果没有使用 Spring HATEOAS,则客户端访问的 URI 都是提前在客户端硬编码的),现在我们就可以做到服务端在不破坏客户端实现的情况下动态的完成 URI 的修改,从而进一步解耦客户端和服务端。

简而言之,现在客户端能干什么事情,在服务端返回的 JSON 中都会告诉客户端,客户端从服务端返回的 JSON 中获取到请求的 URL,然后直接执行即可。如果这个请求地址发生变化的话,客户端也会及时拿到最新的地址。

可能上面的例子小伙伴们感受还不是很明显,我再给大家看一段 JSON:

{
    "tracking_id": "666",
    "status": "WAIT_PAYMENT",
    "items": [
        {
            "name": "book",
            "quantity": 1
        }
    ],
    "_Links": {
        "self": {
            "href": "http://localhost:8080/orders/666"
        },
        "cancel": {
            "href": "http://localhost:8080/orders/666"
        },
        "payment": {
            "href": "http://localhost:8080/orders/666/payments"
        }
    }
}

这是电商系统下单之后等待支付的过程中返回的 JSON,这里的 links 给出了三个:

  • self:访问这个链接可以查看当前订单信息(GET 请求)。
  • cancel:访问这个链接可以取消当前订单(DELETE 请求)。
  • payment:访问这个链接可以支付当前订单(POST 请求)。

这个例子就很直白了,就是在返回的 JSON 中,直接告诉你接下来能做哪些操作,对应的 URL 分别是什么,前端拿到之后直接操作,如果这些操作路径发生了变化,前端也会立马拿到最新的路径。

这就是 Spring HATEOAS 的好处。总之一句话,Spring HATEOAS 提倡在响应返回的 Link 中给出对该资源接下来操作的 URL。这种方式解耦了服务端 URI,也可以让客户端开发者更容易地探索 API。

3. REST 的优缺点

虽然我们现在都鼓励设计 REST 风格的 API,然而 REST 也不全是优点,事物总是具有两面性,REST 的优缺点分别如下。

3.1 优点

  1. 首先,REST 足够简单,有一定 Web 开发经验的小伙伴都可以快速上手 REST。
  2. REST 风格的接口测试起来也非常方便,利用浏览器自带的一些 REST 插件或者是 POSTMAN 之类的工具,就可以非常方便的实现 REST 接口的测试。
  3. 不需要中间代理,简化了系统的结构。
  4. HTTP 对防火墙比较友好。

3.2 缺点

  1. REST 只支持请求-响应的通信方法,不支持服务端推送消息到客户端。
  2. 给请求取一个合适的名字比较困难,特别是有多个相类似的接口时,例如有多个添加接口、多个更新接口等。
  3. 由于没有中间代理,所以请求/响应的时候,服务端和客户端都必须在线。

好啦,跟小伙伴们聊了 REST 和 Spring HATEOAS,感兴趣的小伙伴可以去试试哦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/152070.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[算法与数据结构]——并查集

目录 1. 概论 定义&#xff1a; 主要构成&#xff1a; 作用&#xff1a; 2. 并查集的现实意义 故事引入&#xff1a; 数据结构的角度来看&#xff1a; 3. find( )函数的定义与实现 故事引入&#xff1a; 实现&#xff1a; 4. join( )函数的定义与实现 故事引入&#xff1a; 实现…

c++11 标准模板(STL)(std::forward_list)(三)

定义于头文件 <forward_list> template< class T, class Allocator std::allocator<T> > class forward_list;(1)(C11 起)namespace pmr { template <class T> using forward_list std::forward_list<T, std::pmr::polymorphic_…

Apollo本地快速部署

GitHub项目地址 Gitee项目地址 Apollo&#xff08;阿波罗&#xff09;是携程框架部门研发的分布式配置中心&#xff0c;能够集中化管理应用不同环境、不同集群的配置&#xff0c;配置修改后能够实时推送到应用端&#xff0c;并且具备规范的权限、流程治理等特性&#xff0c;适…

【计算机网络-数据链路层】介质访问控制协议(MAC协议)

文章目录1 静态划分信道——信道划分 MAC 协议1.1 频分多路复用&#xff08;FDM&#xff09;——“并行”1.2 时分多路复用&#xff08;TDM&#xff09;——“并发”1.2.1 同步时分多路复用1.2.2 异步时分多路复用1.3 波分多路复用&#xff08;WDM&#xff09;1.4 码分多路复用…

数据结构进阶 AVL树

作者&#xff1a;小萌新 专栏&#xff1a;数据结构进阶 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;介绍高阶数据结构:AVL树 AVL树AVL树的概念AVL树节点类的定义AVL树的插入AVL树的旋转左单旋右单旋左右双旋右左单旋AVL树的验证…

多线程适用接口及常见类

日升时奋斗&#xff0c;日落时自省 目录 1、Callable接口 1.1、Callable方式 1.2、非Callable方式 2、JUC&#xff08;java.util.concurrent&#xff09;的常见类 2.1、ReentrantLock 2.2、信号量Semaphore 2.3、CountDownLatch 3、线程安全的集合类 3.1、多线程使用A…

【SpringMVC】使用SpringMVC处理JSON格式的数据

目录 一、前言 二、ResponseBody 三、RequestBody 四、HttpMessageConverter 相关文章&#xff08;可以关注我的SpringMVC专栏&#xff09; SpingMVC专栏SpingMVC专栏一、前言我们在使用Servlet处理前端请求&#xff0c;使用Json格式的数据&#xff0c;通常引入外部提供的一些…

拉伯证券|利好来了,145万手封涨停!低位+低市值“无主”股揭秘

二三四五或将完毕多年“无主”局势。 周末大消息不断。蚂蚁集团1月7日在官网发布关于持续完善公司管理的公告。公告显现&#xff0c;蚂蚁集团调整首要股东投票权&#xff0c;强化与阿里巴巴集团的隔离&#xff0c;阿里巴巴开创人马云抛弃了对蚂蚁集团的实践操控权&#xff0c;蚂…

JavaScript 原型链

文章目录原型链本质 - 对象间的关联关系instanceof 和 isPrototypeOf__proto__的大致实现委托原型链 原型链就是一系列对象的链接。通常来说&#xff0c;这个链接的作用是&#xff1a;如果在对象上没有找到需要的属性或者方法引用&#xff0c;引擎就会继续在[[Prototype]]关联的…

餐饮业数字化提速,OceanBase助海底捞变身“云上捞”

在海底捞火锅智慧餐厅&#xff0c;你会看到忙得团团转的机械臂和传菜机器人&#xff0c;顾客在智能点餐系统上下单&#xff0c;机械臂和传菜机器人就会着手备菜、传菜、上菜&#xff0c;服务人员则有更多时间专心应答顾客的各种询问。这种新模式&#xff0c;也为海底捞家喻户晓…

Springboot新手开发 基本总结

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;后端专栏 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;…

网络实验之HSRP协议

一、HSRP协议简介 HSRP&#xff1a;热备份路由器协议&#xff08;HSRP&#xff1a;Hot Standby Router Protocol&#xff09;&#xff0c;是cisco平台一种特有的技术&#xff0c;是cisco的私有协议。该协议中含有多台路由器&#xff0c;对应一个HSRP组。该组中只有一个路由器承…

mac自定义环境变量,mac自定义python变量,自定义通用变量(任意名字)

文章目录几个常用的命令工作原理知道原理后已经有了环境变量没有该环境变量几个常用的命令 功能命令查看当前环境变量echo $PATH编辑文件&#xff08;注&#xff1a;这里是直接编辑bash_profile&#xff09;vi ~/.bash_profile编辑i退出编辑esc回到命令&#xff08;用命令来保…

Apollo实现cron语句的热配置

GitHub项目地址 Gitee项目地址 Apollo&#xff08;阿波罗&#xff09;是携程框架部门研发的分布式配置中心&#xff0c;能够集中化管理应用不同环境、不同集群的配置&#xff0c;配置修改后能够实时推送到应用端&#xff0c;并且具备规范的权限、流程治理等特性&#xff0c;适…

stl algorithms 算法

所有泛型算法&#xff0c;除了少数例外&#xff0c;前两个参数均为一组iterator&#xff0c;用来标识欲遍历容器元素的范围&#xff0c;范围从第一个iterator所指位置开始&#xff0c;至第二个iterator所指位置&#xff08;并不包括&#xff09;结束 如 int arr[3]{1,2,3} ve…

管理客户信息并非易事

客户信息是企业的重要资产&#xff0c;是企业日积累月的价值沉淀&#xff0c;管理客户信息对于企业来说并不是一件容易的事&#xff0c;只有妥善管理客户信息&#xff0c;才能为企业创造更多价值。前言众所周知&#xff0c;客户信息是企业的重要资产&#xff0c;是企业日积累月…

rtu遥测终端机应用及安装介绍

1、设备介绍 设备集遥测终端机功能和视频录像机功能为一体&#xff0c;融合先进的3G/4G/WIFI通信技术、实现水文/水资源/环保212/TCP Modbus/MQTT等数据的采集、视频、图像存储、显示、控制、报警及传输等智能值守功能。 2、设备应用方向 本设备可广泛适用于带视频监测的水…

Jenkins 构建过程中提示 GPG 错误

错误信息如下&#xff1a;[INFO] --- maven-gpg-plugin:3.0.1:sign (sign-artifacts) rets-io --- gpg: no default secret key: No secret keygpg: signing failed: No secret key这个问题的原因应该是我们最近把我们的项目发布到中央 Maven 仓库中&#xff0c;但是发布项目到…

社区发现系列05:图的构建

想要挖掘作弊团伙首先先要构建社交网络图&#xff0c;然后用算法或者策略挖掘作弊团伙&#xff0c;那么如何构建社交网络图呢&#xff1f;下面给大家介绍一些实战经验&#xff0c;主要从电商和互金小额贷款两个业务场景来说&#xff1a; 1、电商业务 由于电商业务涉及的业务场…

《Spring揭秘》读书笔记1:IoC和AOP

1 Spring框架的由来 Spring框架的本质&#xff1a;提供各种服务&#xff0c;以帮助我们简化基于POJO的Java应用程序开发。 各种服务实现被划分到了多个相互独立却又相互依赖的模块当中&#xff1a; Core核心模块&#xff1a;IoC容器、Framework工具类。 AOP模块&#xff1a;S…