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

透过一致组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)及事件处理函数,因此于最终落实中命令还是联合实施之。

图片 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方法,创建新资源

创办一个初资源的常,客户端和服务器是怎交互的为?
图片 2

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

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

对此这API层来说,最关键的责任是用脚的领域建模为资源,并通过HTTP语义暴露出。在这个历程遭到,API层将创一个官领域,它由资源(以及它们的绝无仅有标识符->URL)以及输入和输出的信所构成。底层的小圈子更是简单,这个公开领域及底领域的貌似程度就进一步强。

(单击图片为放开)

图片 3

以斯事例中,我们创建的公开领域与底层的天地要于相似的,但就算是这种简单的圈子,我们为不克直接以根的小圈子暴露出来:这恐怕引致领域的中贯彻叫泄漏出来,而且世界中也未自然带有API层所欲的一切属性。比方说,所有的里边命令还见面为此一个整数来代表并发时所急需的版本号,而在光天化日领域被虽用字符串意味着是特性。我们有点晚将会晤使用这个特性作为ETag,而依据HTTP规格要求,ETag必须是勿透明的。

简简单单的话,我们所创办的明领域表现了里的领域接近,但还要休完全相同。这种公然领域通常给叫做一个视图模型(Vide
Model)。这个术语并无极端可靠,因为这种表达方式感觉上针对公开领域有些排外,将她视为等同种植“哑”模型,因此我们支持于采用一个初术语“输出模型”(output
model)。它将于以到输入和出口消息中(命令和输出模型)。

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

图片 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本条资源作为丰富和去库存量(即签入或移除物品)的不二法门。从精神上说她并未呀高下的分,无非是哪种艺术能再度好地显现资源而已。由于第一栽方法进一步便捷,因此我们就算动这种艺术。

(单击图片为推广)

图片 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实现之。

有关作者

图片 6Ali Kheyrollahi
是平员解决方案架构师、作者、博主、开源软件的撰稿人与贡献者,目前供职于伦敦底如出一辙寒大型电子商务企业。他针对性HTTP、Web
API、REST、DDD和概念模型抱来极大的古道热肠。而于处理实际的作业问题上同时坚持实用性。他以及时同样推行已发12年以上的阅历,并在差不多只优秀企业工作了。他对电脑视觉与机械上园地有深厚的兴趣,并且已宣布了大多首论文。在前,他都是一模一样名叫医生,并作一如既往名为不专科医生工作了5年。可以在此处找到他的博客,此外他当twitter上吗特别活跃,可以透过@aliostad关注外。

查原文地址:Exposing CQRS Through a RESTful
API

admin

网站地图xml地图