REST(REpresentational State Transfer)是 Roy Fielding 博士于 2000 年在他的博士论文中提出来的一种软件架构风格(一组架构约束条件和原则)。在该论文的 中文译本 中翻译是"表述性状态移交"。
原则
网络上的所有事物都被抽象为资源
每个资源都有一个唯一的资源标识符
同一个资源具有多种表现形式(xml,json 等)
对资源的各种操作不会改变资源标识符
所有的操作都是无状态的
资源(Resources)
资源是一种信息实体或者说是一个具体信息,能够被想象出名字。比如多个图书馆,那么便是可使用的图书馆资源,而图书馆内,多个楼层,那么便拥有了多个楼层的资源,各楼层提供了不同服务,那么服务也是资源。在互联网中,可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的 URI(如同一本书,按照书页码去定位哪一页,目的是定位资源)。访问这个特定 URI 便获取到了这个对应的资源。
考虑 Api 设计时,URI 中不能有动词,URI 的目的是定位资源,而具体的对资源的操作,是借助 HTTP 的动词完成,与早期 Api 设计相比,本身的思路是不同的,原来更多的是考虑函数式编程或者叫做面向行为的服务建模,比如 RPC,远程调用一个函数,那么 Api 设计便是会考虑为动词名词格式,而对于 REST 风格来讲,是面向资源的服务建模。而对于资源而言,可以是对象、数据或是查询服务。
资源的组织决定着 URI 的展示方式,对于底层数据库而言,也许 Order 模型有若干张表来支撑存储,对外总体是提供着 Order 服务。这样一来,如果按照底层数据库表来考虑 Api 设计,则会陷入无尽的关系处理中,比如 Order 下有 OrderItem,OrderItem 下有 OrderItemAttachment,如果按照这个思路去实现 Api 设计时,那么 URI 的设计上则会存在多级情况。
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/{id}/OrderItems { "productIds": [ 4, 5, 6 ] }
POST /Api/OrderItemAttachments { "orderItemId": 1, "fileUrl": "xxx" }
嵌套层级结构不会太深,因为太深的层级结构往往也意味着这个聚合的设计或许存在一点问题。
约束设计
对于 Post、Put、Patch 和 Delete 这些操作来讲,面向聚合设计 URI 基本可以有路可循。比如以下一些常见的 URI
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
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