设为首页 加入收藏

TOP

从 Feign 使用注意点到 RESTFUL 接口设计规范(二)
2017-11-10 08:32:57 】 浏览:2835
Tags:Feign 使用 注意 点到 RESTFUL 接口 设计 规范
,契约规范,CRUD对应增删改查操作等等。下面笔者从两个实际的接口来聊聊自己的看法。

根据id查找用户接口:

@FeignClient("user")
public interface UserApi {
    @RequestMapping(value = "/user/{userId}",method = RequestMethod.GET)
    String findById(@PathVariable("id") String userId);
}

这应该是没有争议的,注意前面强调的,@PathVariable(“id”)括号中的id不可以忘记。那如果是“根据邮箱查找用户呢”?很有可能下意识的写出这样的接口:

@FeignClient("user")
public interface UserApi {
  
    @RequestMapping(value = "/user/{email}",method = RequestMethod.GET)
    String findByEmail(@PathVariable("email") String email);
}
  • 首先看看Feign的问题。email中通常包含’.‘这个特殊字符,如果在路径中包含,会出现意想不到的结果。我不想探讨如何去解决它(实际上可以使用{email:.+}的方式),因为我觉得这不符合设计。
  • 再谈谈规范的问题。这两个接口是否是相似的,email是否应该被放到path中?这就要聊到RESTFUL的初衷,为什么userId这个属性被普遍认为适合出现在RESTFUL路径中,因为id本身起到了资源定位的作用,他是资源的标记。而email不同,它可能是唯一的,但更多的,它是资源的属性,所以,笔者认为不应该在路径中出现非定位性的动态参数。而是把email作为@RequestParam参数。

RESUFTL结构化查询

笔者成功的从Feign的话题过度到了RESTFUL接口的设计问题,也导致了本文的篇幅变长了,不过也不打算再开一片文章谈了。

再考虑一个接口设计,查询某一个月某个用户的订单,可能还会携带分页参数,这时候参数变得很多,按照传统的设计,这应该是一个查询操作,也就是与GET请求对应,那是不是意味着应当将这些参数拼接到url后呢?再思考Feign,正如本文的第二段所述,是不支持GET请求携带实体类的,这让我们设计陷入了两难的境地。而实际上参考一些DSL语言的设计如elasticSearch,也是使用POST JSON的方式来进行查询的,所以在实际项目中,笔者并不是特别青睐CRUD与四种请求方式对应的这种所谓的RESTFUL规范,如果说设计RESTFUL应该遵循什么规范,那大概是另一些名词,如契约规范和领域驱动设计。

@FeignClient("order")
public interface BookApi {
    @RequestMapping(value = "/order/history",method = RequestMethod.POST)
    Page<List<Orders>> queryOrderHistory(@RequestBody QueryVO queryVO);
}

RESTFUL行为限定

在实际接口设计中,我遇到了这样的需求,用户模块的接口需要支持修改用户密码,修改用户邮箱,修改用户姓名,而笔者之前阅读过一篇文章,也是讲舍弃CRUD而是用领域驱动设计来规范RESTFUL接口的定义,与项目中我的想法不谋而合。看似这三个属性是同一个实体类的三个属性,完全可以如下设计:

@FeignClient("user")
public interface UserApi {
    @RequestMapping(value = "/user",method = RequestMethod.POST)
    User update(@RequestBody User user);
}

但实际上,如果再考虑多一层,就应该产生这样的思考:这三个功能所需要的权限一致吗?真的应该将他们放到一个接口中吗?实际上,笔者并不希望接口调用方传递一个实体,因为这样的行为是不可控的,完全不知道它到底是修改了什么属性,如果真的要限制行为,还需要在User中添加一个操作类型的字段,然后在接口实现方加以校验,这太麻烦了。而实际上,笔者觉得规范的设计应当如下:

@FeignClient("user")
public interface UserApi {
    @RequestMapping(value = "/user/{userId}/password/update",method = RequestMethod.POST)
    ResultBean<Boolean> updatePassword(@PathVariable("userId) String userId,@RequestParam("password") password);
    
    @RequestMapping(value = "/user/{userId}/email/update",method = RequestMethod.POST)
    ResultBean<Boolean> updateEmail(@PathVariable("userId) String userId,@RequestParam("email") String email);
    
    @RequestMapping(value = "/user/{userId}/username/update",method = RequestMethod.POST)
    ResultBean<Boolean> updateUsername(@PathVariable("userId) String userId,@RequestParam("username") String username);
}
  • 一般意义上RESTFUL接口不应该出现动词,这里的update并不是一个动作,而是标记着操作的类型,因为针对某个属性可能出现的操作类型可能会有很多,所以我习惯加上一个update后缀,明确表达想要进行的操作,而不是仅仅依赖于GET,POST,PUT,DELETE。实际上,修改操作推荐使用的请求方式应当是PUT,这点笔者的理解是,已经使用update标记了行为,实际开发中不习惯使用PUT。
  • password,email,username都是user的属性,而userId是user的识别符号,所以userId以PathVariable的形式出现在url中,而三个属性出现在ReqeustParam中。

顺带谈谈逻辑删除,如果一个需求是删除用户的常用地址,这个api的操作类型,我通常也不会设计为DELETE请求,而是同样使用de

首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Java 小技巧 ( 一 ) : 远程 debug 下一篇编辑 java 逃逸分析

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目