乐趣区

关于api:到底什么样的-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 办法生成的。

当然,其实这块还有很多其余的生成链接的玩法,然而我就不一一介绍了,小伙伴们能够参考官网文档:

  • docs.spring.io/spring-hate…

从下面 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. 因为没有两头代理,所以申请 / 响应的时候,服务端和客户端都必须在线。
退出移动版