透过一致组RESTful API暴露CQRS系统功能。RESTful API 设计最佳实践。

命令和查询责任分开(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)及事件处理函数,因此在最终促成中命令还是一道施行之。

爱博体育app 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方法,创建新资源

开创一个新资源的时,客户端与服务器是怎么交互的也罢?
爱博体育app 2

每当资源集合URL上使POST来创造新的资源过程

  1. 客户端向资源集合URL/employees出殡POST请求。HTTP body
    包含新资源的习性 “Albert Stark”。
  2. RESTful
    Web服务器也新职工生成ID,在那个里面模型中创造员工,并通往客户端发送响应。这个响应的HTTP头部包含一个Location字段,指示创建资源而看的URL。
公开领域的构造

于这API层来说,最重大之权责是以根的领域建模为资源,并通过HTTP语义暴露出来。在这个历程遭到,API层将创设一个公家领域,它由资源(以及它的唯一标识符->URL)以及输入和出口的音讯所构成。底层的园地越来越简单,这个公开领域与底领域的形似程度就是越强。

(单击图片以拓宽)

爱博体育app 3

以是例子中,我们创建的公开领域和底层的世界要于一般之,但就是这种简单的领域,我们为未克一直将脚的园地暴露出:这也许引致领域的中间贯彻叫泄漏出来,而且世界内也无肯定带有API层所急需的周性能。比方说,所有的里命令还见面用一个整数来表示并发时所用的版本号,而在公然领域受到虽然用字符串表示此特性。我们小晚将会晤以是特性作为ETag,而依据HTTP规格要求,ETag必须是免透明底。

简单的话,我们所创办的明领域表现了内部的世界接近,但还要休完全相同。这种公然领域通常给称一个视图模型(Vide
Model)。这个术语并无极端可靠,因为这种表达方式感觉上针对公开领域有些排斥,将她就是等同种植“哑”模型,因此我们赞成被采用一个初术语“输出模型”(output
model)。它以被利用至输入和出口消息遭(命令和输出模型)。

针对具体资源的URL使用PUT方法,来更新资源

爱博体育app 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以此资源作为丰富和去库存量(即签入或移除物品)的方法。从实质上说其从不啊高下的分,无非是呀种方法能够又好地展现资源而已。由于第一种办法越来越便利,因此我们就是利用这种办法。

(单击图片以放开)

爱博体育app 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实现之。

至于作者

爱博体育app 6Ali Kheyrollahi
是一律各类解决方案架构师、作者、博主、开源软件的作者及贡献者,目前供职于伦敦底同等贱大型电子商务企业。他对HTTP、Web
API、REST、DDD和概念模型抱来庞大的热忱。而以处理实际的事体问题达到还要坚持实用性。他于这无异于实践已经来12年以上的经验,并以差不多个优秀企业工作了。他对于电脑视觉及机器上世界有着深厚的兴趣,并且已经发布了差不多篇论文。在前头,他已是一致叫做医生,并当同样叫作非专科医生工作了5年。可以当此地找到他的博客,此外他以twitter上为够呛活跃,可以经过@aliostad关注外。

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

相关文章