/getCustomers
/getCustomersByName
/getCustomersByPhone
/getNewCustomers
/verifyCredit
/saveCustomer
/updateCustomer
/deleteCustomer
可有时却因为没有统一的规范,多人协作时,对于动词的描述上也没有统一,时长出现了类似如下的各类叫法,不能说这种情况有什么弊端,毕竟这种方式也是正常工作着。
/getCustomers
/getAllCustomer
/getCustomerList
/getPagedCustomer
/queryCustomers
/queryAllCustomers
/queryCustomerList
...
相比之下,RESTful Api 提供了更为标准化,规范化的 URL 写法。
GET /Api/Users/{id}
这样便按照 id 值,来唯一定位这一 User
对于资源的单复数格式,尽管规范是尽可能使用复数,但并没有说哪条纪律或是约束限制说一定要使用复数,这无需强制约束,按照自身统一即可。毕竟有些不可数名词,没有复数格式,那么还是沿用本身,而对于整体风格为复数下,却又显得格格不入。
POST /Api/Orders
POST /Api/Orders/{id}/OrderItems
POST /Api/Orders/{id}/OrderItems/{itemId}/OrderItemAttachments
POST /Api/Orders/{id}/OrderItems/{itemId}/OrderItemAttachments/{}/...
...
于数据库而言,表与表间构成了一张庞大的网,有时还不好找到定位资源的入口
如果按照单表进行 URI 设计,那么则成了面向表服务建模,这又造成了底层的服务细节统统对外暴露,因此需要避免创建仅反映数据库内部结构的 API。在领域驱动设计中,聚合这一概念,将具有强相关的实体和值对象纳入到一起,形成独立空间、业务逻辑内聚于聚合之中,同生共死。面向聚合进行 Api 设计,多级路由的嵌套结构缓和许多,如需求上考虑 Order 创建时一定需要有 OrderItem 的存在,那么则对于这两者而言是捆绑的关系,而对于 OrderItemAttachment 而言,不是必要的。那么则可以独立设计聚合(此处忽略底层数据库中表是如何设计的,仅考虑聚合),URI 的设计也围绕着聚合这一资源来进行,这样一来,URI 的设计便成了如下结构
POST /Api/Orders
{
"locationId": 1,
"productIds": [
1,
2,
3
]
}
POST /Api/Orders/{id}/OrderItems
{
"productIds": [
4,
5,
6
]
}
POST /Api/OrderItemAttachments
{
"orderItemId": 1,
"fileUrl": "xxx"
}
嵌套层级结构不会太深,因为太深的层级结构往往也意味着这个聚合的设计或许存在一点问题。
POST /Api/Orders
POST /Api/Orders/{id}/OrderItems
POST /Api/OrderItemAttachment
PUT /Api/Orders/{id}
PUT /Api/Orders/{id}/OrderItems/{itemId}
PUT /Api/OrderItemAttachments/{id}
PATCH /Api/Orders/{id}/Address
PATCH /Api/Orders/{id}/OrderItems/{id}/Amount
PATCH /Api/OrderItemAttachments/{id}/FileUrl
DELETE /Api/Orders/{id}
DELETE /Api/Orders/Batches
DELETE /Api/Orders/{id}/OrderItems/{id}
DELETE /Api/OrderItemAttachments/{id}
POST /Api/Invites/emailTemplate
PATCH /Api/Invites/{id}/Sendmail //Sendmail 作为邮件服务资源
PATCH /Api/Notifications/{id}/MessageStatus
PATCH /Api/Notifications/MessageStatus/batches
PATCH /Api/Orders/{id}/OrderItem/{itemId}/PayStatus
POST /Api/Orders/exports //返回导出资源
POST /Api/exportServices //提交给导出服务资源
POST /Api/exportServices/Sendmail
POST /Api/InviteParseServices //提交给解析服务资源
...
当然也有一些夹杂着动词,习以为常的 Api 设计,如果习惯了,不想改变,仍然可以使用着动词(后续提到该部分违反约束),但若想改变,就得换个思路去考虑设计了
POST /Api/Account/Login
POST /Api/Account/Logout
POST /Api/Account/Register
比如,Login/Logout 操作的目标资源是什么?如果把登录的用户当作在系统中存储的资源来看便可以认为已上线的用户信息,取个资源名字,在线用户(onlineUser),然后对其执行行为。而对于 Register 来讲,则更是容易转换了,注册本身是对 Account 的操作行为,其本质是创建一个没有过的用户。那么直接去掉注册即可了,如认可改变可以按照如下设计,如仍习惯现有,则不改即可,并没有什么约束、纪律限制说一定要遵循。
POST /Api/Accounts
POST /Api/OnlineUsers
//如下需要结合 Authorization,不直接在 URI 中传递参数
DELETE /Api/OnlineUsers
主要是对于查询类的操作,设计起来复杂一些,无论是实际开发中还是按照二八原则,大部分操作都是查询操作,并且查询起来天马行空。先是以下简单的查询
GET /Api/Orders
GET /Api/Orders/{id}
GET /Api/Orders/{id}/OrderItems
GET /Api/Orders/{id}/OrderItems/{id}
// 筛选
GET /Api/Orders?Name=xxx&LocationId=xxx
// 分页
GET /Api/Orders?Page=1&Limit=10
// 也可以拆分成如下两个此处资源为 Page
GET /Api/Orders/Page?Page=1&Limit=10
GET /Api/Orders/PageCount?Page=1&Limit=10
// 排序
GET /Api/Orders?Sort=Name%20DESC
GET /Api/Orders?Sort=Name%20DESC,CreationTime%20ASC
然后再为一些常见场景下的(对于查询类的,聚合的边界应消失了,更多的应该是将各种资源串联起来)
// UI 上需要知道某个资源是否存在
GET /Api/Orders?name=xxx
HEAD /Api/Orders?name=xxx
能够查询到状态码返回 204
找不到状态码返回 404
// 文件下载
GET /Api/OrderFiles/{id}/Url
// 报表分析(将报表分析的结果作为虚拟资源)
GET /Api/AnalyseResults
// 返回指定条件下的总数
GET /Api/Locations/{id}/OrderCount?Status[]&Status[]=2&CreationTime=2022-05-01
// UI 上下拉框所需要的基础数据
GET /Api/Locations/Names?page=1&limit=30&search=xxx
{
"id": "xxx",
"name": "xxx"
}
// 获取最近的循环周期
GET /Api/Plans/{id}/LatestCycleDate
// 获取最近的记录(根据时间,状态过滤后的第一条)
GET /Api/Orders/Latest
...
实际使用中,算了算也只有百分之八十左右的接口是按照 RESTful Api 的规范使用着的,总是有些接口,不能或是难以用简单的描述就能解决。比如如下几个接口,我便直接违反着约束(不能有动词,只能使用名词)。
PATCH /Api/Invites/{id}/Approval
PATCH /Api/Invites/{id}/Decline
PATCH /Api/Invites/{id}/Reject
...
Github中也还是有动词描述 https://docs.github.com/en/rest/codespaces/codespaces#start-a-codespace-for-the-authenticated-userhttps://docs.github.com/en/rest/codespaces/codespaces#stop-a-codespace-for-the-authenticated-userhttps://docs.github.com/en/rest/checks/runs#rerequest-a-check-runhttps://docs.github.com/en/rest/checks/suites#rerequest-a-check-suite如果按照这几个约束条件来看的话,仅当满足三个约束条件的才能认为是 RESTful Api,而满足一个或是两个约束条件的为 Http Api,那么我们或许是一直在追随 RESTful Api 的路上了。面对这部分难以描述或是无法组织的接口,个人认为直接违反一些约束即可,总归是只有少部分接口仅满足一个到两个约束。
{
"code":200,
"message":"",
"data":{
}
}
对 HTTP 的状态码接触越多后,越发觉得思路偏了,不应该将请求响应的状态码与业务中行为的成功与否进行隔离开,因为 HTTP 本身是应用层协议(超文本移交协议),是为业务服务的。如何在网络层面上把一个请求发送出去,再接收到响应,这是 TCP 协议来保障的。假设网络层如果请求失败了,那么应用层都无法进行,因此结合状态码与返回内容(当出现异常时仍然返回状态码与错误描述信息)。如下 HTTP 的状态码覆盖了绝大部分场景。当客户端需要追踪问题时,查看对应请求的状态码,结合其对应的解释说明,便可以去定位相关的问题,当然,前提是真的返回了符合场景下的状态码。
100
–199
)200
–299
)300
–399
)400
–499
)500
–599
)GET /Api/v2/Orders/{id}
GET /Api/Orders/{id}?version=2
GET /Api/Orders/{id}
Custom-Header: version=2
转自:微笑刺客D
链接:https://www.cnblogs.com/CKExp/p/16306835.html
END
关注后端面试那些事,回复【2022面经】
获取最新大厂Java面经