Redian新闻
>
到底什么样的REST才是最佳REST?

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

公众号新闻

说起 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,感兴趣的小伙伴可以去试试哦~


END


聊聊企业开源的底层逻辑



这里有最新开源资讯、软件更新、技术干货等内容

点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦~

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
大S瓜又曝新猛料,到底什么人能体面离婚?惊爆!多伦多华人存了15年的RESP被骗近$4万: 这是给孩子上大学的!南瓜烤了很好吃,你试试加拿大人对医疗保健私有化到底什么态度?连花清瘟回应不利传言,多名医生提醒……到底什么可防新冠?杜海涛暴瘦后又胖成球!?到底什么TM的叫“减肥成功”?到底什么是经济周期?董洁一场直播带货2亿?到底什么品味那么值钱??你有什么样的内心,就会感召什么样的能量刷屏朋友圈的「神秘乱码」,到底什么来头?在美国,到底什么才是“受控释放”?新冠“阳了”,保险公司却拒赔?中国人寿紧急回应!20倍大牛股突然闪崩跌停,被质疑“贸易三角”,到底什么情况?到底什么是Mapping?英国人愣了!10年来首次!中国学生申请英国本科人数下降!到底什么原因?留学申请会变容易吗?卡塔尔王太后、名媛精英都爱的意大利殿堂级羊绒大衣,到底什么来头?刘润:到底什么是“兴趣电商”?李镇西:什么样的师生关系,才是“好的教育”?三体人害怕的加速器,到底什么来头? |【经纬低调分享】悉尼男子持刀割喉妻子,血溅当场!警察赶到现场后惊呼:场面太血腥!到底什么仇什么怨!“你喝过迪士尼的山泉水吗?” 一瓶炒到$10000美元 到底什么来头诺奖得主跨越40年的研究成果:到底什么样的孩子才能成功?到底什么人,盯上了医务人员那点可怜的夜班费?01月31号:刘润对话小马宋:到底什么是4P?空巢老人的周末由拼多多在城里的火热想到的.....61岁刘德华腹肌照曝光!你小子到底什么来头??宋志平:什么样的公司,才是高质量的上市公司我宣布ChatGPT才是真正的糊弄学大师新舊交替田亮女儿近况冲上热搜:那些生在罗马的“富二代”,真实生活到底什么样?单向树洞:到底什么才是“情绪价值”达美乐到底什么来头?我吃到了第一张披萨一文读懂到底什么是CMA?对财务的帮助大吗?这个王一博都在穿的巴西小众帆布鞋到底什么来头?听说好看又好穿!【爱在深秋】--抢跑 《秋窗风雨夕》
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。