lovebet爱博由此同样组RESTful API暴露CQRS系统功能。RESTful API 设计最佳实践。

Young提出的一种将系统的读(查询)、写(命令)操作分离为两种独立子系统的架构模式,   译文出处

指令和查询责任分开(CQRS)是出于Greg
Young提出的同等种植将系统的宣读(查询)、写(命令)操作分离为寡栽独立子系统的架构模式。命令通常是异步执行的,并储存于一个事务型数据库中,而念操作则通常是最后一致的,并且数据来自于解正规化的视图。

原文出处: Philipp
Hauer   译文出处:slane   

正文在此提出并为读者展示同种植也CQRS系统创建同拟RESTful
API的主意。这种方法做了HTTP的语义、REST
API基于资源的风骨,并能处理分布式计算的一些问题,例如最终一致性与并发性。

项目资源的URL应该什么统筹?用名词复数还是用名词单数?一个资源需要有些只URL?用啦种HTTP方法来创造一个新的资源?可选参数应该在何?那些无关乎资源操作的URL呢?实现分页和版本控制的极致好方式是呀?因为起最多的疑问,设计RESTful
API变得慌棘手。在就首文章被,我们来拘禁一下RESTful
API设计,并吃起一个超级实践方案。

除此以外我们尚提供了同样模拟原型API,它白手起家于Greg
Young编写的m-r
CQRS原型之上,后者也为誉为SimplestPossibleThing。m-r可以当是CQRS原型的事实标准,它刺激了过多团组织采用并创CQRS系统。虽然此m-r原型很粗略,但其既能够亮在具体世界被利用RESTful
CQRS系统的少数机遇和挑战了。

每个资源使用有限独URL

资源集合用一个URL,具体有资源用一个URL:

/employees #资源聚合的URL /employees/56 #切实有资源的URL

1
2
3
/employees         #资源集合的URL
/employees/56      #具体某个资源的URL
 

我们在以下部分审阅m-r的天地模型,随后对系特性的API设计进行局部探索。最后,我们将针对有所举行的挑展开讨论,并且讨论一些RESTful
m-r的概念和辩论内容。

所以名词代替动词表示资源

即时让你的API更精简,URL数目还不见。不要这样设计:

/getAllEmployees /getAllExternalEmployees /createEmployee
/updateEmployee

1
2
3
4
5
/getAllEmployees
/getAllExternalEmployees
/createEmployee
/updateEmployee
 

再也好之统筹:

GET /employees GET /employees?state=external POST /employees PUT
/employees/56

1
2
3
4
5
GET /employees
GET /employees?state=external
POST /employees
PUT /employees/56
 

m-r领域

m-r模型是一个透过简化的库存管理体系的圈子模型,你可以创造新库存物品(假设它是某种类型的成品),重命名或吊销激活(即逻辑删除)它们。被撤回激活的品拿不再为用户所展现,而颇具移动的物品都可以让得,并且能看出每个物品的具有细节。你吧会多或者减这些库存物品,指定所进入或者回落的品数量。换句话说,在树库存量之后,就可以开始使用此体系了。

用户用由此同步的询问来查物品列表或是物品细节,对于物品状态的改动将经命令来贯彻。在切实世界面临,命令应该是异步执行之,但出于代码中使用了内存中的风波总线(Event
Bus)及事件处理函数,因此在最后落实中命令还是一道施行之。

lovebet爱博 1

m-r模型实现了CQRS:命令和查询被分别存储于不同之地方,并且各自由系统受到完全不同的一部分开展处理。

除去CQRS之外,m-r为利用了事件本源(Event
Sourcing)作为其的持久化机制。在这种办法遭,对于世界模型的修改会让抓走为同一多样的轩然大波,这些事件会以其被调用的顺序存储起来。为了博有模型的此时此刻状态,需要拿所有事件仍其来的逐条进行重播。换句话说,模型中实体的状态信息是匪会见叫持久化的。举例来说,如果我们创建了一个库存物品,随后用它们更命名两不行,那么我们以会见落一个InventoryItemCreated事件与一定量独InventoryItemRenamed事件,这些事件都见面被保存在事件存储(Event
Store)中。

事件是连的,并且每个事件都蕴含一个本号,用以在连发时进行自我批评。举例来说,如果某库存物品在本子2底根底及进展重新命名,但刚有另一个再次命名发生在跟一个物料及,并使它们的当下版本变为3,那么这种情景就是会见导致出现异常。

指令和天地事件屡见不鲜是相当的涉,当调用了某个命令下,领域模型会倡导并储存一个轩然大波。领域事件是事件源自的木本,它和跨多单边界及下文(bounded
context)的风波不同,往往粒度更周密,并且只是包括所用的无比小数目的信。因此,它并无是一个相符给以不同的边际上下文之间开展集成的家伙。除了行使一个过程内的事件总线之外,m-r还因此到了一个外存中的波存储。这个蕴藏本质就是一个哈希表,它采用模型的id作为键,并且不断跟踪模型中产生的任何事件。

如若需了解CQRS和波本源的还多信息,你可翻阅Greg
Young的随即按照迷你写。

故而HTTP方法操作资源

动URL指定你一旦为此之资源。使用HTTP方法来指定怎么处理这个资源。使用四种植HTTP方法POST,GET,PUT,DELETE可以供CRUD功能(创建,获取,更新,删除)。

  • 获取:使用GET方法得到资源。GET请求没有改变资源的状态。无副作用。GET方法是幂等的。GET方法有独自念之意义。因此,你可健全的以缓存。
  • 创建:使用POST创建新的资源。
  • 更新:使用PUT更新现有资源。
  • 删除:使用DELETE删除现有资源。

2只URL乘以4独HTTP方法就是是相同组大好之力量。看看这表:

POST(创建)GET(读取)PUT(更新)DELETE(删除)

/employees 创建一个新员工 列出所有员工 批量更新员工信息 删除所有员工
/employees/56 (错误) 获取56号员工的信息 更新56号员工的信息 删除56号员工

创立同学上层之REST API

假若你赞成被事先失感受一下最终之落实,可以在此地关押一下一个脚下(暂时性)可运行的原型。我们鼓励公采取fiddler或者浏览器自带的开发工具去检查一下这个简单的以身作则中之HTTP请求。在GitHub上可找到包这套API和一个骨干的Angular应用之源代码。不过我们或如强调,它的落实方式同利用的技巧并非要所在,读者更应关心被统筹方跟HTTP的见。

本着资源集合的URL使用POST方法,创建新资源

缔造一个初资源的时,客户端和服务器是怎么交互的为?
lovebet爱博 2

在资源集合URL上行使POST来创造新的资源过程

  1. 客户端向资源集合URL/employees发送POST请求。HTTP body
    包含新资源的特性 “Albert Stark”。
  2. RESTful
    Web服务器也新员工生成ID,在该中间模型中开创员工,并朝客户端发送响应。这个响应的HTTP头部包含一个Location字段,指示创建资源而看的URL。
当众领域的结构

于这个API层来说,最重点之权责是将脚的领域建模为资源,并经过HTTP语义暴露出。在此过程被,API层将创设一个官领域,它由资源(以及她的绝无仅有标识符->URL)以及输入和输出的信息所做。底层的圈子进一步简单,这个公开领域和底部领域的形似程度就更是强。

(单击图片为拓宽)

lovebet爱博 3

在这个事例中,我们创建的公然领域以及底层的领域或比相似之,但尽管是这种简易的园地,我们也无可知一直将脚的圈子暴露出来:这说不定导致领域的中贯彻为外泄出去,而且世界内也无必然带有API层所要的百分之百性。比方说,所有的内部命令还见面用一个整数来代表并发时所待的版本号,而以明面儿领域面临虽然因此字符串代表这特性。我们有些晚拿会使此特性作为ETag,而基于HTTP规格要求,ETag必须是勿透明底。

简来说,我们所创的当众领域表现了间的小圈子接近,但同时非完全相同。这种公开领域通常为喻为一个视图模型(Vide
Model)。这个术语并无太规范,因为这种表达方式感觉上对公开领域有些排斥,将它便是等同栽“哑”模型,因此我们赞成被以一个新术语“输出模型”(output
model)。它将受运用及输入和出口消息中(命令和出口模型)。

本着具体资源的URL使用PUT方法,来更新资源

lovebet爱博 4

使PUT更新已来资源

  1. 客户端向实际资源的URL发送PUT请求/employee/21。请求的HTTP
    body中带有要创新的属性值(21哀号员工的新名称“Bruce Wayne”)。
  2. REST服务器更新ID为21底职工称,并使HTTP状态码200象征更改成。
资源

俺们蛮当然地想到该发生一个InventoryItem资源,因此我们用世界被之这单根实体暴露吗一个独立的资源,可以就此/api/InventoryItem福利地进行表示。每个库存物品拿就此/api/InventoryItem/{id}进行表示,m-r用了全局唯一标识符(GUID)作为Id。

利用这个独自的到底对象就可完全的见我们的世界了。还有平等栽方式是下/api/InventoryItem/{id}/Stock此资源作为丰富和去库存量(即签入或移除物品)的法子。从精神上说它并未呀高下的分,无非是哪种艺术能够再次好地见资源而已。由于第一栽方法进一步便利,因此我们就动这种办法。

(单击图片为推广)

lovebet爱博 5

推介用复数名词

推荐:

/employees /employees/21

1
2
3
/employees
/employees/21
 

不推荐:

/employee /employee/21

1
2
3
/employee
/employee/21
 

实则,这是个人爱好问题,但复数形式进一步常见。此外,在资源集合URL上用GET方法,它又直观,特别是GET /employees?state=externalPOST /employeesPUT /employees/56。但极紧要的凡:避免复数和单数名词混合使用,这显示特别混乱还易失误。

查询

俺们要简单独查询:GetInventoryItemsGetInventoryItemDetails。这里我们将通过简单个GET方法/api/InventoryItem/api/InventoryItem/{id}暴露出立即有限只查询功能。

GetInventoryItems术会抱仅包含了物品名称Id的一个列表,它见面根据ACCEPT头决定回去JSON或是XML(ASP.NET
Web
API能够支持这同样效)。如果有资源符合给缓存,那么所有的GET请求都来或回缓存数据。GetInventoryItems返回InventoryItemListDataCollection用作出口消息。虽然可以透过数量内容的哈希生成ETag,不过此我们选择用列表中列一样起的Id名称开展哈希后收获的结果作ETag返回给客户端(例如浏览器)。客户端好择以资源缓存起来,并针对性ETag使用If-Non-Match进展标准化请。我们选以资源的max-age设为0,因此客户端的GET会始终以口径请,不过呢可以挑选安装一个人工的逾期时。

GET /api/InventoryItem HTTP/1.1 
Accept:application/json, text/plain, */* 
Accept-Encoding:gzip,deflate,sdch 
If-None-Match:"LdHipfxR7BsfBI3hwqt2BLsno8ic98KmrIA1y67Nnw4="

返回结果

HTTP/1.1 304 Not Modified 
ETag: "LdHipfxR7BsfBI3hwqt2BLsno8ic98KmrIA1y67Nnw4="

GetInventoryItemDetails方法会返回某个库存物品的底细,包括IdNameCurrentCount性,最后一码属性记录了当前底库存数量。虽然其间领域的读取模型(read
model)包含了本子号,但一旦将有数值类的版本号直接当做ETag会有安全性问题,因为客户端可随心所欲地猜测出下一个数值。因此,我们选取了采用高级加密标准(AES)对版本号进行加密后,作为InventoryItemDetails方法的ETag输出。

为每个操作都还实现ETag对于API层来说稍负担过重,因此我们定义了一个IConcurrencyAware接口:

public interface IConcurrencyAware 
{ 
    string ConcurrencyVersion { get; set; } 
}

每个支持ETag的出口模型都使落实者接口,当API层看到某个输出模型支撑这个接口时,就会见宣读取版本号并安装ETag值。另一方面,当API层对条件式GET请求进行响应时,会以转移的ETag与客户端在If-None-Match头中传播的价进行比较。所有这些操作都足以经一个独立的大局filter实现:ConcurrencyAwareFilter

内需专注的是,添加、删除或重命名某单库存物品常应当要物品列表的缓存失效。请看下的事例(条件式GET请求的逻辑是在浏览器端完成的,不需专门编写代码实现):

GET /api/InventoryItem HTTP/1.1 
If-None-Match:"CWtdfNImBWZDyaPj4UjiQr/OrCDIpmjVhwp8Zjy+Ok0="

回到结果是一个状态码为200底完好应,并且包含了一个初的ETag值:

HTTP/1.1 200 OK 
Cache-Control:max-age=0, private 
Content-Length:68 
ETag:"0O/961NRFDiIwvl66T1057MG4jjLaxDBZaZHD9EGeks=" 
Content-Type:application/json; charset=utf-8; domain-
model=InventoryItemListDataCollection; version=1.0.0.0; 
format=application%2fjson; schema=application%2fjson; is-text=true 
...

要小心Content-Type头包含了附加的参数,这是对此“媒体类型的五种级别”(或者简称5LMT)概念的平等栽实现,这种艺术不是拿具备消息还填到一个单独的令牌(token)中,而是以不同的参数来抒发对用户中之例外级别的多寡,能够抒发不同级别之发因此信息。下文会对这个主题召开更加的议论。

对可选的、复杂的参数,使用查询字符串(?)。

无引进做法:

GET /employees GET /externalEmployees GET /internalEmployees GET
/internalAndSeniorEmployees

1
2
3
4
5
GET /employees
GET /externalEmployees
GET /internalEmployees
GET /internalAndSeniorEmployees
 

以为您的URL更有些、更简洁。为资源设置一个基本URL,将可摘的、复杂的参数用查询字符串表示。

GET /employees?state=internal&maturity=senior

1
2
GET /employees?state=internal&maturity=senior
 
命令

查询普通会映射到GET方法,而令则用映射到POST、PUT、DELETE和PATCH方法。将HTTP谓词映射到CRUD操作是一致种植流行的传统,但每当实世界中格外少会将谓词和数据库操作一一对应。实际上,REST
API并无以对持久化存储之上的一个略包装,相反,它是依赖引用户去打听工作领域、操作和工作流的均等扇门。因此她要能不因让特定的称呼词去发表有维度的意图。

一律栽常见的方式是用远程过程调用(RPC)风格的资源,例如/api/InventoryItem/{id}/rename。虽然其看起来确实去除了对某种谓词的依靠,但它违反了REST面向资源的变现能力。我们要记住,资源是一个名词,HTTP谓词则代表动词和动作,而起描述的消息(REST的宗旨之一)则是达其它维度信息及用意的伎俩。实际上,在HTTP消息遭到所含有的下令就应该可以描述任何人为的操作了。但是,完全依赖让要求体中的信息也有它们和谐之题目,因为要求体通常是当流传递的,要于辩认出她的具体操作之前获得整个请求体有时是匪可能做到的,而且就也不是同样栽明智之做法。这里,我们用显示同种基于5LMT中的第4级别(即世界模型)处理要的方法,命令的品种将含有在Content-Type头中的某某参数内。

PUT /api/InventoryItem/4454c398-2fbb-4215-b986-fb7b54b62ac5 HTTP/1.1  
Accept:application/json, text/plain, */* 
Accept-Encoding:gzip,deflate,sdch 
Content-Type:application/json;domain-model=RenameInventoryItemCommand

如此这般即便能将请是地输送给服务端相应的拍卖措施了。那这种措施是否将过多的消息泄露给客户端了呢?并非如此。输入输出消息的schema(以及名称)是当着领域的一律有些,客户端必须能整体地走访到其,因此它们凭借让schema也是以我们所预期的。

至于客户端的兑现只所以了极其少量之代码,这里用了一个AngularJS*的装饰(decorator)封装了$http劳动,它能读取这个原型的归内容,并且能当Content-Type头着在额外的参数信息。只要保持JavaScript构造函数*的称呼不变换就从未有过问题。

俺们既缓解了甄别时正给调用的方法的题材,接下要将指令按照语义映射到相应的HTTP谓词。在以命映射到叫词时,选择正确谓词的重中之重不仅仅在于语义,同样要考虑幂等性(至于谓词的安全性则任需顾忌,因为其他一个命谓词都是勿安全的)。PUT、PATCH和DELETE是幂等的,而POST则无是幂等的(多次调用一个幂等的谓词的结果及单调用一潮是平等的)。

使用HTTP状态码

RESTful Web服务承诺运用合适的HTTP状态码来响应客户端请求

  • 2xx – 成功 – 一切还很好
  • 4xx – 客户端错误 –
    如果客户端起误(例如客户端发送无效请求或不给授权)
  • 5xx – 服务器错误 – 如果服务器出错误(例如,尝试处理要时错)
    参考维基百科上的HTTP状态代码。但是,其中的大部HTTP状态码都不见面被用到,只会用中的如出一辙粗片。通常会就此到瞬间几个:2xx:成功3xx:重定向4xx:客户端错误5xx:服务器错误

    200 成功 301 永久重定向 400 错误请求 500 内部服务器错误
    201 创建 304 资源未修改 401未授权
    403 禁止
    404 未找到
CreateInventoryItemCommand

从今CRUD范式的角度来说,CreateInventoryItemCommand良自然地适用于POST方法。(这里仅展示主要之腔信息)

POST /api/InventoryItem HTTP/1.1 
Content-Type:application/json;domain-model=CreateInventoryItemCommand  

{"name": "CQRS Book"}

回到的响应如下:

HTTP/1.1 202 Accepted 
Location: http://localhost/SimpleCQRS.Api/api/InventoryItem/
109712b9-c3d5-4948-9947-b07382f9c8d9

欠操作以在location头信息中回到这个用为创造的库存物品(因为具有操作都是异步执行之)的URL地址。

回有用的荒谬提示

除开当的状态码之外,还应当以HTTP响应正文中提供实用的错误提示和详尽的叙说。这是一个例子。
请求:

GET /employees?state=super

1
2
GET /employees?state=super
 

响应:

// 400 Bad Request { “message”: “You submitted an invalid state. Valid
state values are ‘internal’ or ‘external'”, “errorCode”: 352,
“additionalInformation” : “http://www.domain.com/rest/errorcode/352” }

1
2
3
4
5
6
7
8
// 400 Bad Request
{
    "message": "You submitted an invalid state. Valid state values are ‘internal’ or ‘external’",
    "errorCode": 352,
    "additionalInformation" :
    "http://www.domain.com/rest/errorcode/352"
}
 
DeactivateInventoryItemCommand

像前文所述,取消激活库存物品便代表一样不善逻辑删除。此外,删除操作是幂等的,因为反复勾一个库存物品的力量以及同等涂鸦去是如出一辙的。因此我们拿祭DELETE选项作为取消激活某个物品的办法(该法包含一个缺损的方法体)。

DELETE /api/InventoryItem/f2b75f21-001a-4eed-b8f3-35bf5e4e9b0d HTTP/1.1 
Content-Type:application/json;domain-model=DeactivateInventoryItemCommand  

{}

归来的响应如下:

HTTP/1.1 202 Accepted

尽管如此也可以方法体中传递id,但于URL中早就提供了id信息。DeactivateInventoryItemCommand构造函数的唯一任务是不错地设置domain-model其一参数。

用小驼峰命名法

用小驼峰命名法作为性能标识符。

{ “yearOfBirth”: 1982 }

1
2
{ "yearOfBirth": 1982 }
 

甭用下划线(year_of_birth)或大驼峰命名法(YearOfBirth)。通常,RESTful
Web服务将被JavaScript编写的客户端采用。客户端会将JSON响应转换为JavaScript对象(通过调用var person = JSON.parse(response)),然后调用那性能。因此,最好遵循JavaScript代码通用标准。
对比:

person.year_of_birth // 不推荐,违反JavaScript代码通用标准
person.YearOfBirth // 不引进,JavaScript构造方法命名 person.yearOfBirth
// 推荐

1
2
3
4
person.year_of_birth // 不推荐,违反JavaScript代码通用规范
person.YearOfBirth // 不推荐,JavaScript构造方法命名
person.yearOfBirth // 推荐
 
RenameInventoryItemCommand

RenameInventoryItemCommand比由外命令来说还有趣一点。首先,重命名一个库存物品也即是开展改动,因此下PUT谓词是最最适于的。另一方面,如果您方重命名某个物品时,你的同事也于品味用那重命名也另外一个名的口舌会什么呢?这便是一个出现问题。HTTP通过If-Unmodified-SinceIf-Match供了对资源开展并发修改时的掩护体制。因为咱们使用了ETag,因此即使相应地安装If-Match

PUT /api/InventoryItem/f2b75f21-001a-4eed-b8f3-35bf5e4e9b0d HTTP/1.1 
Content-Type:application/json;domain-model=RenameInventoryItemCommand 
If-Match:"DL1IsUoH709K+N5TXFzlQeQI5arO8r/U0SzXcRhuXLc="  

{"newName": "CQRS Book 1"}

AngularJs的controller会传递ETag值,并传播模型中,之后于基准式PUT请求时展开下。如你所见,ETag的值仅仅是针对性天地模型中版本号的同样栽表现,但我们针对其开展加密以满足HTTP规格的要。服务端获取到是价后进行解密并回复成版本号的数值。如果版本号不配合,领域模型就会丢来一个ConcurrencyException异常,在API层的ConcurrencyExceptionFilterAttribute好像捕获到这好后,会为HTTP语义的措施展现该大。

HTTP/1.1 412 Precondition Failed

这事例十分好地证明了HTTP的起如何跟CQRS的起检查体制相互结合。

于URL中劫持在版本号

从头到尾,都采取版本号发布您的RESTful
API。将版本号放在URL中因为凡必需的。如果您有未配合和破坏性的反,版本号以于您可知再次便于的发布API。发布新API时,只需要以增加版本号被之数字。这样的话,客户端好熟练的迁徙至新API,不会见为调用了不同的新API而陷入困境。
采用直观的 “v” 前缀来表示后面的数字是版本号。

/v1/employees

1
2
/v1/employees
 

汝免需用次级版本号(“v1.2”),因为若不应该频繁的失去发布API版本。

CheckInItemsToInventoryCommand和RemoveItemsFromInventoryCommand

眼看点儿个命就越来越有意思了。我们以为库存中进入或者去一些物品。从某个地方来说,这种操作是针对性库存物品的数据进行创新,因此可以以那个实现吗一个PUT(也许PATCH更适用)方法。但为当时有限独令并非幂等(比如说,调用CheckInItemsToInventoryCommand两蹩脚当加上两涂鸦库存),因此最好符合之谓词实际上是POST。

客户端将在Content-Type头信息被之参数中安装领域模型的称谓,如同咱们前面所见的相同。

POST /api/InventoryItem/f2b75f21-001a-4eed-b8f3-35bf5e4e9b0d HTTP/1.1 
Content-Type:application/json;domain-model=CheckInItemsToInventoryCommand  

{"count": "230"}

返回的响应是同的:

HTTP/1.1 202 Accepted

供分页信息

一次性返回数据库有资源不是一个吓主意。因此,需要提供分页机制。通常使用数据库被肯定的参数offset和limit。

/employees?offset=30&limit=15 #返回30 到 45的员工

1
2
/employees?offset=30&limit=15       #返回30 到 45的员工
 

万一客户端从未招这些参数,则答应利用默认值。通常默认值是offset = 0limit = 10。如果数据库检索很缓慢,应当减少limit值。

/employees #返回0 到 10的员工

1
2
/employees       #返回0 到 10的员工
 

此外,如果你使用分页,客户端需要明白资源总数。例: 请求:

GET /employees

1
2
GET /employees
 

响应:

{ “offset”: 0, “limit”: 10, “total”: 3465, “employees”: [ //… ] }

1
2
3
4
5
6
7
8
9
{
  "offset": 0,
  "limit": 10,
  "total": 3465,
  "employees": [
    //…
  ]
}
 
HTTP的别样方面

兑现HTTP的有的别样地方呢会带动一些补,HEAD也是一个着重之谓词,它的应结果以及GET方法同样,但返回的响应体中莫包外内容。我们吧保有GET资源且实现了HEAD谓词,例如:

HEAD /api/InventoryItem HTTP/1.1 
Accept:application/json, text/plain, */* 
Accept-Encoding:gzip,deflate,sdch

将返回

HTTP/1.1 200 OK 

ETag: "LdHipfxR7BsfBI3hwqt2BLsno8ic98KmrIA1y67Nnw4="

实际以促成着见面以HEAD请求转向被GET方法的处理函数,而框架本身会于最终负责移除返回的情。这等同多元实现都是自动触发的,因此于响应中得以是地得到ETag。

另外一个需要贯彻的重中之重谓词是OPTIONS,这个谓词可以用来生成API文档,不过我们这边只是简短的回该资源支持之持有谓词:

OPTIONS /api/InventoryItem/f2b75f21-001a-4eed-b8f3-35bf5e4e9b0d HTTP/1.1

它以赶回如下内容:

HTTP/1.1 200 OK 
Allow: GET,POST,OPTIONS,HEAD,DELETE,PUT 
Content-Length: 46 
Content-Type: application/json; charset=utf-8; domain-model=String%5b%5d; version=4.0.0.0; 
format=application%2fjson; schema=application%2fjson; is-text=true  

["GET","POST","OPTIONS","HEAD","DELETE","PUT"]

吁小心,响应中之Allow头对OPTIONS请求来说是要的。不过HTTP规格本身并不曾点名OPTIONS响应体中切实写法,因此我们便将同意的谓词作为一个字符串数组返回(注意,在domain-model参数中之String[]是经过UrlEncoded方法编码的结果)。可以采取是名为词生成适合各种schema和语言要求的API文档。

除此之外这些点子之外的其余调用都见面回去一个术无找到(method not
found)
要么405状态码,ASP.NET Web API自身已落实了这同效应:

PUT /api/InventoryItem HTTP/1.1  

{}

其以回来:

HTTP/1.1 405 Method Not Allowed 
Allow: POST,GET,HEAD,OPTIONS  

{"message":"Http Method not supported"}

非资源要用动词

偶API调用并无关乎资源(如计量,翻译还是更换)。例:

GET /translate?from=de_DE&to=en_US&text=Hallo GET
/calculate?para2=23&para2=432

1
2
3
GET /translate?from=de_DE&to=en_US&text=Hallo
GET /calculate?para2=23&para2=432
 

以这种情形下,API响应不会见回到外资源。而是实行一个操作并以结果回到给客户端。因此,您该在URL中以动词而无是名词,来了解的区分资源要与非资源要。

讨论

当时等同有些以详细描述某些理论概念,以及我们的主宰着有于不方便,或者可能引起争议的局部。

设想特定资源搜索与超资源搜索

供针对性一定资源的查找怪容易。只需要使用相应的资源集合URL,并将搜字符串附加到查询参数中即可。

GET /employees?query=Paul

1
2
GET /employees?query=Paul
 

若是如本着负有资源提供全局搜索,则要用任何艺术。前文提到,对于未资源要URL,使用动词而非是名词。因此,您的找网址可能如下所示:

GET /search?query=Paul //返回 employees, customers, suppliers 等等.

1
2
GET /search?query=Paul   //返回 employees, customers, suppliers 等等.
 
而摘的产出检查

每当m-r最初的实现中,所有命令(除了CreateInventoryItemCommand,它既隐式地包含了值为0的版本号)都富含一个平头型的CurrentVersion字段。而此本子中将其修改也而选取的(即C#未遭的可空类型)。

于单方面,服务端应该背确保自身状态的完整性。因此她不可知、也无应有依靠让客户端所提供的版本号。并发检查是作为一个特性提供给客户端的,而无是服务端用以保证模型完整性的编制。如果客户端关心并发行为,那它们就是可以选择性地发送版本号,这曾经经过当ETag中之加密信息提供给其了。要铭记的是,并作检查和服务端的事件版本号是不同之概念,后者是劳动端的里贯彻机制。

一方面,对于一些操作来说,并作检查是绝非意义的。举例来说,如果个别个客户端在同一时间(调用CheckInItemsToInventoryCommand方法)添加了20单库存物品,并且它都抱有版本号n,那么中起一个指令就见面败,但这种失败是未必要之,因为我们的确用添加40单物品。这种问题在高访问量的状下会给加大。想象一下,如果大气底用户涌入亚马逊网站去买哈利波特的新颖一望,在大多数状下他们还见面遇见并发问题。

在HTTP中施行PUT(和PATCH)操作时会觉得出现是一个可选的检查,这或多或少并非偶然。虽然出现检查可以异步执行,但咱得着力确保它必须一起执行,因此当我们回来状态码202(已领)时,就代表服务端已经肯定了未曾起冲突情况的起。

于应参数中上加浏览其他API的链接

好图景下,不会见于客户端好组织采用REST API的URL。让咱们想想一个例证。
客户端想如果看员工的薪酬表。为这,他要掌握他可以通过在员工URL(例如/employees/21/salaryStatements)中附加字符串“salaryStatements”来访问薪酬表。这个字符串连接老轻出错,且难以维护。如果你改变了拜访薪水表的REST
API的艺术(例如变成了/employees/21/salary-statement/employees/21/paySlips),所有客户端都将中断。
更好的方案是于应参数中补充加一个links字段,让客户端可活动变更。
请求:

GET /employees/

1
2
GET /employees/
 

响应:

//… { “id”:1, “name”:”Paul”, “links”: [ { “rel”: “salary”, “href”:
“/employees/1/salaryStatements” } ] }, //…

1
2
3
4
5
6
7
8
9
10
11
12
13
//…
   {
      "id":1,
      "name":"Paul",
      "links": [
         {
            "rel": "salary",
            "href": "/employees/1/salaryStatements"
         }
      ]
   },
//…
 

若果客户端了依赖links备受的字段获得薪资表,你转移了API,客户端将老抱一个实用之URL(只要您改变了link字段,请求的URL会自动更改),不会见半途而废。另一个好处是,你的API变得可自描述,需要写的文档更不见。
当分页时,您还好加上获取下同样页或达亦然页的链接示例。只需要提供适当的摇和限量的链接示例。

GET /employees?offset=20&limit=10

1
2
GET /employees?offset=20&limit=10
 

{ “offset”: 20, “limit”: 10, “total”: 3465, “employees”: [ //… ],
“links”: [ { “rel”: “nextPage”, “href”: “/employees?offset=30&limit=10”
}, { “rel”: “previousPage”, “href”: “/employees?offset=10&limit=10” } ]
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  "offset": 20,
  "limit": 10,
  "total": 3465,
  "employees": [
    //…
  ],
  "links": [
     {
        "rel": "nextPage",
        "href": "/employees?offset=30&limit=10"
     },
     {
        "rel": "previousPage",
        "href": "/employees?offset=10&limit=10"
     }
  ]
}
 
媒体类型的五栽级别(5LMT)和创新的媒体类型

在社区里常见的相同栽做法是创建新的传媒类型,通常称为制作新的传媒类型。举例来说:

Content-Type:application/vnd.InventoryItemListDataCollection.1.0.0.0+json;

这种用特别的主意表示有媒体类型的子类型已经成了相同种植通用的履(已经实际变成平等种约定了),它将子系统分解为部分特定的、或者是正经的要素,并经过+号连接在同步。已经稍经过注册之传媒类型应用了这种约定,例如application/rss+xmlapplication/atom+xml。这有限个示范处于媒体类型级别中之第3级别(或者叫schema级别),而application/xml则处于第2级别(format级别)。某种意义上说,application/atom+xml虽是均等栽application/xml色,它们采取同样之format,而前者还指明了会见使ATOM
schema。

尽管就无异横定会以未来版的HTTP规格中得承认,但她从不缓解媒体类型不断提高的问题。首先,使用外不报之传媒类型且是HTTP规格所未提倡的,使用上述档的Content-Type价值吗是同样。实际上,如果我们用以具备API中也五个不同媒体级别的即兴组合都注册一种媒体类型,那互联网号码分配局(IANA)恐怕需要动员一好批判人去专门从事这个范畴宏大的天职了。另一方面,许多客户端系统利用基于dictionary的媒体类型去处理这种求,它们将未克应付新创建的媒体类型。

就此下5LMT能够允许现有的客户端继续按照之前的计健康干活,而再度上进的客户端则可采用还胜级别的信,它们都是用作单身的实业提供的。

相关阅读

  • 笔者写的平等首关于在Java中测试RESTful服务之极品实践的文章。
  • 作者强烈推荐一本书Brain Mulloy’s nice
    paper,作为这篇稿子的基础。

    1 赞 7 收藏 1
    评论

经过一个公然之天地保障间领域是关键所在

用服务端的内贯彻进行抽象对客户端的话是大关键之。如同之前所陈述,为比小之圈子所开创的公然领域及内部领域会较一般,但哪怕是以m-r这个示例中,我们为不克用中间领域直接暴露出,而要创造一个独立的范,它们显现了客户端能够吸收及相的音

咱还应有用公开领域文档化,并显现让客户端。这一边之拓展值得关注,因为已发各种不同的方法和实行开始发水面了(从WADL到Swagger、RAML和RestDown等等)。

结论

不仅仅通过同样效REST
API暴露CQRS是唯恐的,而且HTTP语义的丰富性也教我们会以它的根基及编制一仿照流畅而中的API。整个工艺流程包括创造一个是因为命和查询(输入输出消息)组成的明领域,以及能处理并发和缓存的各种资源。此外,我们尚需要以里面领域的询问与指令映射为HTTP谓词,并且利用状态码以表现状态转换与良。使用5LMT将助长创造了RESTful,而休是远程过程调用风格的资源。所有这些还足以经过一个特别小但可以运作的原型应用进行展现,该原型是透过ASP.NET
Web API和AngularJS实现之。

至于作者

lovebet爱博 6Ali Kheyrollahi
是一样号解决方案架构师、作者、博主、开源软件的作者及贡献者,目前供职于伦敦之同等贱大型电子商务企业。他对HTTP、Web
API、REST、DDD和概念模型抱来大幅度的热情洋溢。而以处理实际的事体问题达到还要坚称实用性。他于这无异履已发出12年以上之阅历,并以差不多单优秀企业工作过。他对于电脑视觉与机械上世界有坚实的兴趣,并且已昭示了差不多首论文。在头里,他一度是同等名叫医生,并作同一名为不专科医生工作了5年。可以以此地找到他的博客,此外他于twitter上啊生活跃,可以透过@aliostad关注他。

翻看原文地址:Exposing CQRS Through a RESTful
API