Design RESTfulAPI —— 从领域建模到 API 设计

REST简介

RESTful,是目前非常流行的一种互联网软件架构,由于其结构清晰、易于理解、便于扩展,正得到越来越多开发者的青睐。

什么是REST

REST(REpresentational State Transfer),首次出现是在2000年Roy Thomas Fielding的博士论文中,它值得是一组架构约束条件和原则。满足这些约束条件和原则的应用程序和设计就是RSETful的。为了理解什么是REST,我们首先需要理解下面几个概念。

  • 资源(Resource)
    REST是“表现层状态转化”,其中暗含的主语便是资源,即资源的表现层状态转化。那么在REST的定义中,什么是资源呢?其实很好理解,我们平时上网所看到的一篇文章、一首歌曲、一个视频等,都可以算作资源。这些资源都可以通过URI来定位,即一个URI表示一个资源。
  • 表现层(REpresentation)
    一个资源,也就是一个信息实体,它可以有多重不同的表现形式。例如:文本可以用txt格式表现,也可以用json或xml的格式表现,这就是表现层的意思。
    通过URI可以确定一个资源,但是如何确定资源的表现形式呢?应该通过HTTP请求头的Accept和Content-Type字段指定。因此,严格来说,如果我们采取了RESTful架构,那么URI中是不应该有.jsp或者.html的后缀名的。因为URI只是用于定位资源,而不负责资源的表现形式。
  • 状态转化(State Transfer)
    访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,肯定涉及到数据和状态的变化。而HTTP协议是无状态的,那么这些状态肯定保存在服务器端,所以如果客户端想要通知服务器端改变数据和状态的变化,肯定要通过某种方式来通知它。
    客户端能通知服务器端的手段,只能是HTTP协议。具体来说,就是HTTP协议里面表示操作方式的动词,如:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。

综合上面的解释,我们可以对RESTful架构做一个简单总结:

  1. 每一个URI代表一种资源
  2. 客户端和服务器之间,资源以某种形式进行传递,即传递资源的表现层
  3. 客户端通过不同的HTTP动词(POST、PUT/PATCH、GET、DELETE、HEAD、OPTIONS)对资源进行操作,从而达到“表现层状态转化”的目的。

REST成熟度模型

在Roy Thomas Fielding的论文中,对REST架构风格做了以下几点约束。

  • Client-Server 客服端-服务器模式
  • Stateless 无状态
  • Cacheable 可缓存
  • Layered System 分层系统
  • Code on Demand 按需扩展
  • Uniform interface 统一接口

严格来说,符合所有这些约束的服务才能被称为 RESTful,但是在实际开发中,这需要付出较为高昂的成本,可能我们常见的服务都不是完全 RESTful 的,那么如何去判断一个服务有多么 RESTful 呢?Richardson 提出的 REST 成熟度模型可以帮助我们分析。模型对于REST服务的成熟划分了如下四个等级。

  • 第0级
    只使用一个 URI 作为服务端口,使用 HTTP 协议传输数据。SOAP 和大多数 WS-* 服务都是属于这个级别的,XML-RPC 和 POX 也是。这种做法相当于把 HTTP 这个应用层协议降级为传输层协议,HTTP 头用来保证传输,而所有的业务逻辑都包含于有效载荷中。

  • 第1级
    使用多个 URI,不同的 URI 代表不同的调用入口,但只使用同一个 HTTP 方法传输数据。目前与第0级的区别在于:第0级相当于传入参数的远程函数调用,而目前需要首先获取相应资源再调用相应方法,可以类比于面向对象中首先获取对象标识符然后调用相应方法。

  • 第2级

    使用多个 URI,不同的 URI 代表不同的资源,同时使用多个 HTTP 方法操作这些资源,例如使用POST/GET/PUT/DELETE 分别进行 CRUD 操作,这时候 HTTP 头和有效载荷都包含业务逻辑,例如 HTTP 动词对应相应操作,HTTP 状态码对应操作结果的状态。目前与前两级服务的区别在于:相比于只使用 POST 的第0级和第1级服务,更为合理地使用了所有 HTTP 动词。

  • 第3级

    最后这个级别引入了HATEOAS(Hypertext As The Engine Of Application State),超媒体控制(Hypermedia Control)的关键在于它告诉我们下一步能做什么,以及对应的资源URI。目前与第2等级的区别在于:客户端在知道服务端入口的情况下,不再需要任何服务端的先验知识,可以根据服务端的返回完成与服务端的交互。

glory-of-rest.png

API设计方法原则

  • API设计接轨业务流程的详细分析与设计

    对客户而言,API如同一本说明书,告诉客户有哪些功能可用,以及使用的具体方式;而对服务提供团队而言,API体现为团队对外的责任。因此,只有立足于对业务流程与客户需求的详细分析,才能通过API封装复杂的业务逻辑,从而明确团队需要对外提供的服务。

  • 团队统一建模方法,统一技术语言

    业务流程的分析可以帮助我们建立业务模型,从而借助模型进一步分析核心业务数据的流动,以便于后续通过模型和数据流动设计合理的 API。业务模型的设计接轨于业务流程的分析,同时也是 API 设计的前提。不仅如此,在团队中,业务模型可以作为不同角色的团队成员交流的工具:例如,Devloper 可以通过模型与 BA 确认业务需求。统一的建模方法,可以最大程度保证各种角色的团队成员对于业务的理解是一致的。从业务模型中,我们可以抽象出 REST 语义下的资源,资源间的关系可以通过业务数据的流向分析。数据的流动会造成资源状态的变化,而我们要进一步抽象的API便可以表征资源状态的变化过程。

  • 结合业务含义正确使用HTTP动词

    在REST语义下,HTTP各动词含义如下表所示:

    方法 描述 幂等(Y/N)
    POST 根据客户端提供的数据创建一个资源 N
    GET 返回资源的当前展现 Y
    PUT 根据客户端提供的数据替换指定资源,或者创建一个新的资源 Y
    DELETE 删除某个资源 Y
    HEAD 返回资源的元信息(如Last-Modified等) Y
    PATCH 部分更新资源 N
    OPTIONS 获取当前资源信息,比如当前资源支持哪些方法的信息 Y

    根据RFC-2616,幂等是指一次和多次请求某一个资源应该具有相同的副作用。幂等的方法意味着请求成功执行所得到的结果不依赖于该方法被执行的次数。例如:通过 PUT 方法将某个资源的 satus 属性置为success,那么无论是第一次执行还是多次执行,获得的结果都是相同的,即执行完成之后都是 status=success。

API设计详细步骤

梳理业务流程

业务流程的梳理,是我们工作的起点。Event Storming是一种有效的帮助我们梳理业务的方法,Event Storming的具体步骤可以参考这篇文档,本文不再赘述。

建立系统模型

完成业务模型的梳理之后,下一步就是建立系统业务模型了,笔者采用的方法是四色建模,InfoQ上的这篇文章已经做了详细介绍。简单总结如下:

  1. 以满足业务运行为前提,寻找需要追溯的事件。
  2. 根据需要追溯的事件,寻找事件相关的时标性对象。所谓时标性对象,一般与时间有紧密相关关系。
  3. 寻找时标对象周围的人、事、物,并分析它们之间的关系。
  4. 从人、事、物中抽象角色。
  5. 用描述性对象补充说明系统中的人、事、物。

那么,我们如何判断我们通过上述方法建立的模型是可用的呢?答案便在于业务数据流。我们可以在建立的业务系统模型上,模拟业务事件的发生,分析业务数据流是否能够在模型上流通,从而判断我们建立的模型能否支撑我们的业务。

以笔者参与的一个实际项目为例:某公司技术人员管理许多数字资产,业务部门的人员需要通过一定的策略对数字资产的状态进行检查,并产生对应的报表。业务建模过程如下:

  • 寻找需要追溯的时标事件
time-interval.png
  • 寻找时标对象周围的人/事/物
object-beside-time-interval.png
  • 从中抽象角色


    role-of-object.png
  • 把一些信息用描述对象补足


    description-of-object.png
  • 寻找模型中的聚合、引用等关联关系

relationships-of-objects.png
  • 识别聚合根
aggregation-root.png

识别资源及设计URI

根据业务模型,我们分析其中的资源和聚合根,并以每个聚合根为起点,开始设计 URI。在上面的例子中,我们的聚合根为 User 和 policy,因此URI设计如下表。

编号 URI
1 /users
2 /users/{uid}
3 /users/{uid}/tasks
4 /users/{uid}/tasks/{tid}
5 /users/{uid}/tasks/{tid}/reports
6 /users/{uid}/tasks/{tid}/reports/{rid}
7 /users/{uid}/assets
8 /users/{uid}/assets/{aid}
9 /policies
10 /policies/{pid}

结合HTTP动词定义RESTful API

结合第一部分说明的在 REST 语义下各 HTTP 动词的含义,分析每个 URI (资源)在这些动词下是否有业务含义,分析如下表。

POST GET DELETE PATCH PUT HEAD OPTIONS
1
2
3
4
5
6
7
8
9
10

结合具体的业务场景和操作者,可以分析每个 API 应该返回的状态码。下表给出了部分(有关 user 和 asset ) API 的使用场景与返回状态码的结果。

WHO URI METHOD SCENE RESPONSE
Any /users POST 创建用户 成功创建201
Any /users POST 创建用户 信息有误400
Admin /users GET 查询所有用户信息 成功获取200
!Admin /users GET 查询所有用户信息 非法获取403
Admin or Uid /users/{uid} GET 查询某个用户信息 成功获取200
Admin or Uid /users/{uid} GET 查询某个用户信息 用户不存在404
!{Admin or Uid} /users/{uid} GET 查询某个用户信息 非法获取403
Uid /users/{uid} PATCH 修改某用户个人信息 成功修改204
Uid /users/{uid} PATCH 修改某用户个人信息 修改有误400
!Uid /users/{uid} PATCH 修改某用户个人信息 非法修改403
Admin or Uid /users/{uid}/assets POST 创建资产 成功创建201
Admin or Uid /users/{uid}/assets POST 创建资产 信息有误400
!{Admin or Uid} /users/{uid}/assets POST 创建资产 非法创建403
Admin or Uid /users/{uid}/assets GET 查询某用户所有资产信息 成功获取200
!{Admin or Uid} /users/{uid}/assets GET 查询某用户所有资产信息 非法获取403
Admin or Uid /users/{uid}/assets DELETE 批量删除某用户的某些资产 成功删除204
Admin or Uid /users/{uid}/assets DELETE 批量删除某用户的某些资产 信息有误400
!{Admin or Uid} /users/{uid}/assets DELETE 批量删除某用户的某些资产 非法删除403
Admin or Uid /users/{uid}/assets/{aid} GET 查询某资产详情 成功获取200
Admin or Uid /users/{uid}/assets/{aid} GET 查询某资产详情 找不到资产信息404
!{Admin or Uid} /users/{uid}/assets/{aid} GET 查询某资产详情 非法获取403
Admin or Uid /users/{uid}/assets/{aid} PATCH 修改某资产信息 成功修改204
Admin or Uid /users/{uid}/assets/{aid} PATCH 修改某资产信息 修改有误400
Admin or Uid /users/{uid}/assets/{aid} PATCH 修改某资产信息 找不到资产信息404
!{Admin or Uid} /users/{uid}/assets/{aid} PATCH 修改某资产信息 非法修改403

分析资源关系实现HATEOAS

通过上面的分析,我们得到了每个 API 的返回 Http 状态码,而对于其返回体的设计,可以根据资源及其相关资源的关系来确定。例如 user 和 asset 有关联关系,那么对于GET /users/{uid} 而言,示例返回的 body 体如下:

{
  "data": {
      "url": "/users/1",
      "attributes": {
          "id": 1,
          "name": "felix",
          "email": "felix@example.com"
      },
      "relationships": {
          "assets": {
              "url": "/users/1/assets"
          }
      }
  }
}

用户通过该 API 可以得到用户的详细信息,而通过返回体可以拿到获取该用户下 assets 的API 而无需事先知道这个 API 的存在。理想情况下,资源与资源见的关系组成一张有向关系图,用户从任何一个资源入手,均可以遍历他有权限访问的所有资源。

自动化Doc (Swagger)

设计好的 RESTful API 可以借助 Swagger 形成文档,从而指导开发。最新的 Swagger 支持 Open API 3.0 规范,文档更新方便,并且支持文档的执行,具备较为完善的生态圈,推荐用其发布我们的 API 文档。

参考链接

Richardson Maturity Model

运用四色建模法进行领域分析

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 一说到REST,我想大家的第一反应就是“啊,就是那种前后台通信方式。”但是在要求详细讲述它所提出的各个约束,以及如...
    时待吾阅读 3,417评论 0 19
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • 去年有段时间得空,就把谷歌GAE的API权威指南看了一遍,收获颇丰,特别是在自己几乎独立开发了公司的云数据中心之后...
    骑单车的勋爵阅读 20,482评论 0 41
  • API定义规范 本规范设计基于如下使用场景: 请求频率不是非常高:如果产品的使用周期内请求频率非常高,建议使用双通...
    有涯逐无涯阅读 2,521评论 0 6
  • 笔记 RESTful架构风格概述 RESTful架构风格 RESTful架构风格最初由Roy T. Fieldin...
    plutoese阅读 12,586评论 3 58