65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Azure 上的 CQRS -查询端

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (3投票s)

2014年4月14日

CPOL

3分钟阅读

viewsIcon

12523

命令查询分离或职责架构的查询端。

引言

这篇文章展示了一种创建 RESTful 接口的方法,用于实现 Command Query Responsibility Segregation(命令查询职责分离)架构的查询端。它使用 VB.NET 实现,但是很容易转换成 C#。

背景

简单来说,CQRS 是一种架构模式,将命令(执行操作)与查询(请求信息)分开。 实际上,这意味着命令端和查询端不需要共享一个通用模型。 这种垂直分离层通常与命令/查询的定义和实现(在命令处理程序或查询处理程序中)之间的垂直分离层相匹配。

根据我的经验,当您与一个分布式团队在很短的时间内开发应用程序时,CQRS 非常有用,因为它降低了“模型争用”的风险(如果每个人都在同时更新模型)。 它也适用于需要对应用程序中发生的所有更改进行审计跟踪的财务场景。

挑战

我想在查询端解决几个影响架构的问题:

最小化有效载荷

传递状态信息以及任何维护状态的系统都会阻碍扩展。 实际上,这意味着每个查询都必须包含满足请求所需的所有信息。

独立开发

前端(在本例中为 Web 服务)和查询逻辑的实际实现之间需要一个剪切层。 这允许更改 **how**(如何)部分,而不会影响 **what**(什么)部分。

查询定义和查询处理程序

因此,该架构分为两个组成部分:(严格的) 查询定义(说明正在请求什么)和相关的查询处理程序(执行服务查询的工作)。

查询定义

所有查询定义都继承自一个接口 - IQueryDefinition - 它唯一地标识查询类型、特定实例及其需要的任何参数。

Public Interface IQueryDefinition

    ''' <summary>
    ''' The unique key by which this query instance can be identified
    ''' </summary>
    ''' <remarks>
    ''' This allows queries to be persisted and for the query handler 
    ''' to record whether or not it has
    ''' executed a given query.  This should not be assumed to be sequential.
    ''' </remarks>
    ReadOnly Property InstanceIdentifier As Guid

    ''' <summary>
    ''' The human-readable name of the query
    ''' </summary>
    ''' <remarks>
    ''' For a high-frequency or low data use scenario an enumerated type 
    ''' can be used but for most cases a readable text
    ''' name for the query is preferable.
    ''' </remarks>
    ReadOnly Property QueryName As String

    ''' <summary>
    ''' Add a parameter to the list of parameters for this query
    ''' </summary>
    ''' <param name="name">
    ''' The name of the parameter to add
    ''' </param>
    ''' <param name="index">
    ''' The index of the parameter (0 if not applicable)
    ''' </param>
    ''' <param name="value">
    ''' The value to use for the parameter
    ''' </param>
    Sub AddParameter(ByVal name As String, ByVal index As Integer, ByVal value As Object)

    ''' <summary>
    ''' The set of parameters for this query
    ''' </summary>
    ''' <remarks>
    ''' This may be null to indicate that a query has no parameters
    ''' </remarks>
    ReadOnly Property Parameters As IEnumerable(Of QueryParameter)

End Interface

扩展此接口,使其还定义查询的预期返回类型。

''' <summary>
''' Interface for any class that is a definition of a query to be passed to the query handler
''' </summary>
''' <remarks>
''' Because the query handler uses IoC, 
''' each query must have its own distinct definition class.
''' This interface and the QueryDefinitionBase class allow for 
''' common functionality to be enforced
''' across these classes.
''' </remarks>
Public Interface IQueryDefinition(Of TResult)
    Inherits IQueryDefinition

End Interface

根据您的业务场景,您可能还希望添加描述查询上下文的属性 - 何时提交、用于提交的地址和凭据,以及要应用于它的任何优先级。 这可以通过在查询定义周围添加一个包装器接口来完成,以便上下文仍然与查询本身所需的参数保持不同。

查询处理程序

查询处理程序获取查询定义并返回所需的 TResult。 定义类和处理程序类之间存在一对一的映射,例如,可以使用 Unity 来解析它们。

Public Interface IQueryHandler(Of Out TResult)

    ''' <summary>
    ''' Execute the query and return the results to the calling application
    ''' </summary>
    ''' <param name="query">
    ''' The query (and any parameters) to execute
    ''' </param>
    ''' <returns>
    ''' The result set as specified by the query handler definition
    ''' </returns>
    Function Handle(ByVal query As IQueryDefinition) As TResult

End Interface

查询处理程序的工作是验证查询上下文的正确性,然后使用查询参数从底层数据源执行数据检索,最后将其强制转换为查询定义预期的返回类型。

将 URI 路由到适当的查询定义

所有查询都来自相同的根 URL,因此路由到正确查询是通过 URI 模板完成的(因此,URI 模板必须唯一地标识查询及其参数)。

这是通过让每个查询定义都有一个 static 函数来返回其 URI 模板并在 WCF 服务定义中使用它们来完成的。

    <OperationContract()>
    <WebGet(ResponseFormat:=WebMessageFormat.Json,
            RequestFormat:=WebMessageFormat.Json,
            BodyStyle:=WebMessageBodyStyle.Wrapped,
            UriTemplate:=GetBazaarsForClientQueryDefinition.UriTemplate)>
    Function GetBazaarsForClient(ByVal clientId As String, ByVal query As String) _
             As IReadOnlyList(Of BazaarSummary)

历史

  • 2014 年 4 月 14 日:初始版本
© . All rights reserved.