如何在 REST、CQRS 和 EventSourcing 一起做的同时支持 REST 中的命令?
How to support Command in REST while doing REST, CQRS and EventSourcing together?
考虑以下用于联系人资源的粗粒度 REST API
POST /api/contacts
GET /api/contacts
GET /api/contacts/:id
PUT /api/contacts/:id
DELETE /api/contacts/:id
考虑对联系人资源使用事件源,即验证命令并存储事件。所以必须存储每个事件,包括每个字段级别的更改。
CreateContactCommand -> | Contact("john", "doe", 25) | -> ContactCreatedEvent
FirstNameChangeCommand -> | Contact("jane", "doe", 25) | -> FirstNameChangedEvent
LastNameChangeCommand -> | Contact("jane", "dear", 25) | -> LastNameChangedEvent
AgeChangeCommand -> | Contact("jane", "doe", 30) | -> AgeChangedEvent
现在,结合 REST 和 EventSourcing。
执行 REST,客户端如何与上述标准 REST APIs 通信以进行字段级更改以在服务器端 REST 端点生成命令?
主要问题是,如何设计 REST API 使其也可以支持最终支持事件源的命令?
如果有人能阐明这一点,将不胜感激。
CQRS 和事件溯源既不是 API 设计原则,也不是顶层架构。不过,如果您想 'expose' 您的 API 作为 基于任务的 API,您可以公开 link 作为联系人 资源。
GET /contacts/1234
回应
200 OK
<contact>
<atom:link href="/contacts/1234/first-name" rel="first-name" />
<atom:link href="/contacts/1234/last-name" rel="last-name" />
<atom:link href="/contacts/1234/age" rel="age" />
<first-name>Jane</first-name>
<last-name>Doe</last-name>
<age>25</age>
</contact>
此处假设您将 API 更改为 true level 3 REST API.
此外,/contacts/1234
将只接受 GET
和 DELETE
( 不 PUT
)请求。如果客户想要改变,例如联系人的名字,它必须跟在关系类型为 first-name
的 link 之后,并针对该资源发出 PUT
请求:
PUT /contacts/1234/first-name
<first-name>John</first-name>
此处除 first-name
字段 PUT
之外的任何其他内容都应忽略或拒绝。
因此,当服务收到针对 first-name 资源的 PUT
时,这是一个更改联系人名字的命令。
这仍然不是一个正确的基于任务的API,因为它没有捕获为什么名字变化,但我希望你能理解。
REST 是 Web 的分布式应用架构风格。 Web 有自己的规则和约束,它是表示和资源的领域,而不是任务、命令或查询。
Web 的级别低于您的应用程序。在设计 HTTP 资源时,您必须考虑缓存、版本控制、可重复性、性能、Web 上下文中的松散耦合,因此您可能不想让它们与域实体完全匹配,更不用说这些实体的属性了。此外,HTTP 的动词数量非常有限,当动词让你失望时,资源不一定是描述任务或动作的最佳选择。
因此,在您的域中有意义的命令与(资源,动词)对之间的严格 one-to-one 对应并不总是可取或适当的。您可能想要更进一步,为 REST 客户端设计您自己的域应用协议,以便根据更精细的规则与服务器对话。正如 Mark 指出的那样,DAP 反映在超媒体链接和转换关系中,但您也可以使用自定义 content-types 来更好地描述请求或响应中的有效负载类型。例如,它们可以包含您发送的域命令的类型。
如果您查看 this article,您会发现(POST
、api/inventoryItem/{id}
)这对夫妇在 "Resources" 下的图表中并不是唯一的。它可用于传输 RemoveItemsFromInventoryCommand
或 CheckInItemsToInventoryCommand
。您如何在 HTTP 请求级别指定它是通过使用自定义内容类型 header : Content-Type:application/json;domain-model=RemoveItemsFromInventoryCommand
或 Content-Type:application/json;domain-model=CheckInItemsToInventoryCommand
.
考虑以下用于联系人资源的粗粒度 REST API
POST /api/contacts
GET /api/contacts
GET /api/contacts/:id
PUT /api/contacts/:id
DELETE /api/contacts/:id
考虑对联系人资源使用事件源,即验证命令并存储事件。所以必须存储每个事件,包括每个字段级别的更改。
CreateContactCommand -> | Contact("john", "doe", 25) | -> ContactCreatedEvent
FirstNameChangeCommand -> | Contact("jane", "doe", 25) | -> FirstNameChangedEvent
LastNameChangeCommand -> | Contact("jane", "dear", 25) | -> LastNameChangedEvent
AgeChangeCommand -> | Contact("jane", "doe", 30) | -> AgeChangedEvent
现在,结合 REST 和 EventSourcing。
执行 REST,客户端如何与上述标准 REST APIs 通信以进行字段级更改以在服务器端 REST 端点生成命令?
主要问题是,如何设计 REST API 使其也可以支持最终支持事件源的命令?
如果有人能阐明这一点,将不胜感激。
CQRS 和事件溯源既不是 API 设计原则,也不是顶层架构。不过,如果您想 'expose' 您的 API 作为 基于任务的 API,您可以公开 link 作为联系人 资源。
GET /contacts/1234
回应
200 OK
<contact>
<atom:link href="/contacts/1234/first-name" rel="first-name" />
<atom:link href="/contacts/1234/last-name" rel="last-name" />
<atom:link href="/contacts/1234/age" rel="age" />
<first-name>Jane</first-name>
<last-name>Doe</last-name>
<age>25</age>
</contact>
此处假设您将 API 更改为 true level 3 REST API.
此外,/contacts/1234
将只接受 GET
和 DELETE
( 不 PUT
)请求。如果客户想要改变,例如联系人的名字,它必须跟在关系类型为 first-name
的 link 之后,并针对该资源发出 PUT
请求:
PUT /contacts/1234/first-name
<first-name>John</first-name>
此处除 first-name
字段 PUT
之外的任何其他内容都应忽略或拒绝。
因此,当服务收到针对 first-name 资源的 PUT
时,这是一个更改联系人名字的命令。
这仍然不是一个正确的基于任务的API,因为它没有捕获为什么名字变化,但我希望你能理解。
REST 是 Web 的分布式应用架构风格。 Web 有自己的规则和约束,它是表示和资源的领域,而不是任务、命令或查询。
Web 的级别低于您的应用程序。在设计 HTTP 资源时,您必须考虑缓存、版本控制、可重复性、性能、Web 上下文中的松散耦合,因此您可能不想让它们与域实体完全匹配,更不用说这些实体的属性了。此外,HTTP 的动词数量非常有限,当动词让你失望时,资源不一定是描述任务或动作的最佳选择。
因此,在您的域中有意义的命令与(资源,动词)对之间的严格 one-to-one 对应并不总是可取或适当的。您可能想要更进一步,为 REST 客户端设计您自己的域应用协议,以便根据更精细的规则与服务器对话。正如 Mark 指出的那样,DAP 反映在超媒体链接和转换关系中,但您也可以使用自定义 content-types 来更好地描述请求或响应中的有效负载类型。例如,它们可以包含您发送的域命令的类型。
如果您查看 this article,您会发现(POST
、api/inventoryItem/{id}
)这对夫妇在 "Resources" 下的图表中并不是唯一的。它可用于传输 RemoveItemsFromInventoryCommand
或 CheckInItemsToInventoryCommand
。您如何在 HTTP 请求级别指定它是通过使用自定义内容类型 header : Content-Type:application/json;domain-model=RemoveItemsFromInventoryCommand
或 Content-Type:application/json;domain-model=CheckInItemsToInventoryCommand
.