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

用于 ESB 的 VirtualService

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (31投票s)

2008年2月19日

CPOL

37分钟阅读

viewsIcon

101232

downloadIcon

1218

本文介绍了使用Microsoft .NET FX 3.5技术为企业服务总线 (ESB) 设计、实现和使用虚拟服务 (VirtualService)。

目录

特点

  • 松耦合设计模式
  • 无需编码即可实现工作流服务虚拟化
  • 模型驱动设计
  • WCF 连接性
  • WF 编排(已类型化和未类型化消息)
  • 内置引导/加载服务
  • 基于业务应用程序域 (AppDomain) 托管服务
  • 配置、WSDL、XOML、规则和 XSLT 的元数据存储在存储库中
  • 同步和异步连接模式
  • 发布/订阅功能
  • 基于 XSLT 元数据进行消息中介
  • 内置 WS-Resource Transfer 中介器
  • 可插入企业服务总线 (ESB) 的能力
  • WCF 和 WF 可扩展性支持
  • IIS7/WAS 和自托管
  • .NET FX 3.5 技术

引言与概念

Microsoft 近日发布了 .NET FX 3.5 版本,其中引入了一种名为 WorkflowService 的新的上下文驱动服务模型。该模型基于 Windows Communication Foundation (WCF) 和 Windows Workflow Foundation (WF) 技术。它代表了暴露工作流的连接性的逻辑模型。工作流中的服务操作被抽象到新的上下文驱动活动中,例如基于契约优先或工作流优先声明的ReceiveActivity

业务连接层(由客户端(通道)或新的上下文驱动活动(SendActivity)表示)可以以同步或异步方式透明地使用工作流服务,就像使用其他标准服务一样,通过不同的物理传输(如 TCP、HTTP、MSMG、管道等)实现各种消息交换模式 (MEP),如输入/输出、请求/应答、会话、双工等。这是一个非常开放的连接范例,能够根据业务需求定制和扩展所有元素。

此外,WorkflowService 还可以编排服务操作。这种通用的服务连接和操作逻辑模型可以通过模型优先工具,基于元数据进行声明式创建。这是 Workflow Service 模型的一个强大功能,因为元数据存储在服务和工作流的知识库(称为资源存储库)中,允许在特定的应用程序域 (AppDomain) 中创建和启动连接性和操作。

让我们继续服务抽象。如前所述,服务操作由顺序工作流或状态机工作流模型表示。从连接性的角度来看,WCF 模型由连接性元数据(如地址、绑定、契约 (ABC))和行为描述。WF 模型由 XOML 元数据或 CLR 类型声明的工作流定义描述。

通过将实际业务服务操作封装到预处理、处理和后处理活动中,与连接性和消息中介相结合,WorkflowService 可以代表虚拟服务 (VirtualService) 进行中介,以松散耦合的方式透明地使用业务层。

下图显示了连接到 ESB 的 WorkflowService

如上图所示,WorkflowService 由存储在元数据中的资源表示,用于声明 WCF 和 WF 模型。工作流由 XOML/规则元数据激活,用于预/后处理以及调用实际业务服务。

注意,工作流编排侧重于将服务操作虚拟化作为逻辑模型的一部分。其业务逻辑驱动集成过程。有许多种类的编排,基于消息中介、分派、路由、业务虚拟化等。此外,XOML 和规则是设计时创建并存储在知识库(服务存储库)中的元数据的一部分,它们不编排实际业务流程,只进行预处理和后处理以及如何调用业务流程。这就是由模型驱动并由元数据表示的虚拟服务 (VirtualService) 的策略和概念。

现在,我们知道了通过 WorkflowService 模型实现的虚拟服务 (VirtualService) 背后的原理。下图显示了本文中使用的它的符号。注意,应用程序中的虚拟服务 (VirtualServices) 没有数量限制,它独立于业务模型。

虚拟服务 (VirtualService) 存在于可以访问业务对象、服务和工作流的应用程序域 (AppDomain) 中。下图显示了一个逻辑层中虚拟服务 (VirtualServices) 的示例,它们位于同一应用程序域内的私有业务层之上。

当然,虚拟服务 (VirtualService) 与业务层、服务或单个服务操作之间没有边界限制。虚拟服务 (VirtualService) 可以根据业务模型需求组合(虚拟化)不同的服务操作。

下图显示了此示例,其中前端层发布了位于内部防火墙后面的私有业务服务的一部分。

请注意,虚拟服务 (VirtualService) 不需要重新构建和重新编译解决方案中的更改,因为它基于元数据模型,并且托管具有预构建的引导服务代理。上图显示了来自私有业务服务(WCF、Web服务、Remoting 等)的服务操作的虚拟化。

在设计时,建模工具将基于私有服务生成 WSDL/mex 文档,作为虚拟服务 (VirtualService) 元数据的一部分,用于响应客户端。模型驱动架构是一种逻辑上集中在存储库中的元数据企业解决方案,而物理上则根据部署模式进行分散。建模是应用程序在企业网络中设计、模拟和部署的重要组成部分。没有合适的工具,处理和管理更复杂的解决方案,例如端点、消息中介、XOML、规则等,将非常困难。

好的,回到在应用程序域 (AppDomain) 中托管虚拟服务 (VirtualService)。将业务模型映射到物理模型,使我们能够将逻辑业务分组并隔离到由应用程序域 (AppDomain) 表示的物理层中。这种模型策略对于企业架构的可靠性和可管理性至关重要。

下图显示了一个具有独立业务应用程序域 (AppDomains) 的应用程序进程。

如上图所示,应用程序进程将自动创建一个具有特殊内置 ESB 服务代理的默认应用程序域 (Default AppDomain)。该代理负责创建和管理特定的应用程序域 (AppDomain) 以托管虚拟服务 (VirtualServices),这些服务基于元数据。注意,建模将创建运行时引导/加载操作所需的所有元数据,例如应用程序域 (AppDomain) 的名称、标识符等。

这是虚拟服务 (VirtualService) 的一项强大功能,模型可以根据业务需求决定其托管方式。业务应用程序域 (Business AppDomain) 中的虚拟服务 (VirtualServices) 可以即时加载和重新加载,顺序任意,而不会中断其他不相关的业务。

ESB 和虚拟服务 (VirtualService)

使用虚拟服务 (VirtualService) 是完全透明的,由元数据抽象,在元数据中声明了“*谁*”以及“*如何*”可以与服务和操作通信。这是一种不同的编程风格,称为声明式编程,其中解决方案被划分为由元数据驱动的预构建通用基础设施组件,允许在逻辑模型中管理解决方案。

下图显示了服务与其消费者之间使用可管理基础设施(企业服务总线)的逻辑连接。

如您在上图中所见,企业服务总线 (Enterprise Service Bus) 代表了业务层之间逻辑连接的实现。在设计/建模时,工具负责将业务模型映射到物理模型。建模的结果是存储在存储库中的元数据。为了在运行时理解这些元数据,ESB 具有预构建的通用基础设施(组件),如适配器、连接器、引导/加载程序等。

下图显示了模型驱动的 ESB 策略。

在部署时,企业服务存储库 (Enterprise Service Repository) 中由元数据描述的业务模型将根据部署模式部署到LocalRepository。注意,LocalRepository 代表了引导/加载服务实时元数据的缓存。

企业服务存储库 (Enterprise Service Repository) 包含企业解决方案的逻辑模型,其中虚拟服务 (VirtualServices) 通过连接器 (Connector) 逻辑地连接到企业服务总线 (Enterprise Service Bus)。连接器 (Connector) 在存储库中表示为虚拟服务 (VirtualService) 元数据的资源。

下图显示了虚拟服务 (VirtualService) 作为连接器将业务服务连接到 ESB 的逻辑位置。

正如您所见,虚拟服务 (VirtualService) 是业务服务的中间件。虚拟服务 (VirtualService) 由 XOML、规则、XSLT 等元数据驱动,以中介消息并将其转发到下一个目标,如私有(或公共)服务或 ESB。下图显示了一个虚拟服务 (VirtualService) 具有嵌入式 ESB 适配器的场景。

当然,虚拟服务 (VirtualService) 也可用于消息中介,如下图所示。

以上就是关于虚拟服务 (VirtualService) 和元数据在 ESB 模型中的位置的描述。让我们回到我们的虚拟服务 (VirtualService)。

如前所述,在 Bootstrap/Loader 使用之前,每个虚拟服务 (VirtualService) 都必须在存储库中声明。存储库有两个可能的级别:本地级别和企业级别。本地存储库保存映射到每个应用程序主题的物理部署模式的元数据。运行时应用程序中的 Bootstrap 启动服务需要此元数据。本地存储库可以通过 MMC 控制台工具在物理级别进行管理。

下图显示了虚拟服务 (VirtualService) 作为私有业务服务外观 (facade) 服务的定位。

本地存储库可以存储在应用程序程序集、配置文件、文件系统或数据库中。使用本地存储库使我们能够将虚拟服务 (VirtualServices) 用作私有中央应用程序配置的一部分。

下图显示了一个配置文件示例,其中LocalRepository 部分描述了 Bootstrap/Loader 的元数据。

正如您所见,有一个endpointName 属性用于在LocalRepository 部分中远程查询元数据。基本上,虚拟服务 (VirtualService) 也可以在不考虑在 ESB 模型中使用的情况下本地配置。当然,在这种情况下,虚拟服务 (VirtualService) 在特定应用程序中单独管理。另一方面,在本地存储库中拥有ESB.DomainLoader 将使我们的应用程序能够随时以完全透明的方式被虚拟服务 (VirtualServices) 托管。

虚拟服务 (VirtualService) 中介

虚拟服务 (VirtualService) 的概念基于 WorkflowService 模型。基本上,有两种服务中介的位置。第一个基于 WCF 可扩展性,使用自定义扩展进行绑定、服务行为、操作、消息和参数的检查。这种中介是通过将强类型中介器注入输入和/或输出管道来提供的。这些中介器的插件位置是配置文件中的system.serviceModel 部分。Microsoft SvcConfigEditor 工具可用于处理此部分。

第二个服务/操作中介的位置是 XOML 工作流。这种中介为虚拟服务 (VirtualService) 提供了许多功能,此外,我们已经有一个工具可以正确编排工作流和规则,生成其元数据(XOML、规则)。虚拟服务 (VirtualService) 中的 XOML 工作流没有限制,它可以完全是业务服务解决方案,但从视图的设计模式来看,此 XOML 应处理预/调用/后处理,而不是实际的业务处理器。基于此,我们可以说,虚拟服务 (VirtualService) XOML 负责操作(消息和交换模式)的中介以及将业务处理器与 ESB 管道隔离开来。

下图显示了业务处理器(位于单独的顺序工作流中)与自定义活动(如 Transformer_IN 和 _OUT)表示的消息中介的封装。

下图显示了 XOML 作为虚拟服务 (VirtualService) 元数据一部分的示例。

消息交换模式 (MEP) 中介

使用虚拟服务 (VirtualService) 中的新ReceiveActivity 可以抽象虚拟服务 (VirtualService) 中的服务操作。此复合活动允许根据指定的消息交换模式处理传入的服务消息。例如,请求/应答模式将请求将值返回给调用者,而单向输出模式则相反。

下图显示了在调用者只需要接收确认返回值,而实际业务流程将在之后以发布/订阅方式处理的场景中,如何通过 MEP 进行更改。

现在,让我们专注于虚拟服务 (VirtualService) 操作契约。基于 WCF 模型,虚拟服务 (VirtualService) 可以针对具有已类型化或未类型化消息的服务操作进行编排。

已类型化操作

具有已类型化消息的操作契约允许接收和处理基于方法签名的直接操作参数。这种类型的操作适用于具有已知服务操作的预构建服务,例如 WS-*(WS-Eventing、WS-ResourceTransfer 等)。虚拟服务 (VirtualService) 契约通过ListenActivity 和基于操作数量的EventDrivenActivity 分支映射到 XOML 工作流。

下图显示了两个操作契约的 XOML 示例。EventDrivenActivity 的每个分支都有一个ReceiveActivity 用于特定的操作契约。

WS-ResourceTransfer 的 XOML 示例

如上例所示,虚拟服务 (VirtualService) 可以处理 WS_Transfer 契约的四个操作。每个操作都有自己的自定义复合活动来处理消息中介(请参见ReceiveActivity 内的彩色活动)和调用业务处理器。

另一个示例:由 WS-ResourceTransfer 契约驱动的 ESB 上的虚拟服务 (VirtualService) 用于资源存储。

未类型化操作

具有未类型化消息的操作契约比已类型化操作提供了更大的灵活性和虚拟化能力。在虚拟服务 (VirtualService) 中使用未类型化操作的概念基于创建通用契约,该契约将以完全透明的方式对任何已类型化和未类型化的消费者有效。换句话说,我们希望将原始消息传递给 ReceiveActivity 进行处理,具体取决于建模。

以下代码片段是具有未类型化操作的服务契约示例。

[ServiceContract]
public interface IGenericContract
{
    [OperationContract(Action="*", ReplyAction="*")] 
    [TransactionFlow(TransactionFlowOption.Allowed)]
    Message ProcessMessage(Message message);
}

如以下 XOML 模板示例所示,ReceiveActivity 接收的传入原始消息可以根据 Action 头选择到正确的逻辑分支。

未类型化操作使我们能够创建具有不同操作(WSDL)的复合服务,将消息中介器置于现有服务中,更改消息交换模式等。服务中介可以在建模时完成,这是为 Bootstrap/Loader 服务生成元数据的结果。

下图显示了一个 XOML 示例,其中传入的原始消息在 Policy 活动中进行验证,然后根据 Action 值传递到特定分支进行处理。对于此由未类型化消息驱动的虚拟服务 (VirtualService),有许多不同的可能操作,包括返回 WSDL 文档的/mex 操作,请参见以下示例。

带 XOML 状态机的虚拟服务 (VirtualService)

基本上,虚拟服务 (VirtualServices) 是为 XOML 顺序工作流声明的(参见所有示例),但没有限制使用它们来定义状态机工作流。在这种情况下,每个状态将接收一个事件消息(已类型化或未类型化),就像在顺序工作流中一样。在虚拟服务 (VirtualService) 中使用状态机的优点是简化事件驱动的、长时间运行的业务流程的控制过程,其中上下文业务对话需要通过多个状态进行流动,例如:订单、验证、付款、发货等。

下图显示了一个虚拟服务 (VirtualService) 的示例,其中服务中介由状态机编排。

WCF 服务(无 XOML)

如前所述,虚拟服务 (VirtualService) 基于 WorkflowService 模型。在某些情况下(路由器、分派器、发布者...),WCF 服务模型也可以用于操作契约的虚拟化。在这些情况下,消息只能通过服务扩展组件进行中介,这不如 XOML 工作流灵活。

下图显示了基于 WCF 服务的虚拟服务 (VirtualService)。

以上就是关于虚拟服务 (VirtualService) 在企业服务总线 (Enterprise Service Bus) 范围内的介绍以及本地使用。

我假设您已经了解了 WCF 和 WF 技术及其可扩展性;因此,我将更侧重于引导虚拟服务 (VirtualServices) 的设计和实现,这只是 ESB 设计和实现中一小部分但非常基础的组件。

好的,让我们开始设计,然后进行实现。

设计与实现

虚拟服务 (VirtualService) 的概念基于元数据驱动模型。松耦合的 WCF/WF 配置模型由存储在system.ServiceModel 部分组中的数据表示,该数据只能在应用程序配置文件中使用。.NET FX 没有内置直接支持来配置其他元数据源,如数据库、文件系统等。

虚拟服务 (VirtualService) 的设计和实现需要在 WCF/WF 模型之上解决以下层:

  • 服务元数据结构(WSDL、XOML、规则、XSLT、端点、绑定、行为、工作流、扩展等)
  • 托管元数据结构(进程、应用程序域 (AppDomain) 等)
  • 基于元数据托管服务和工作流服务 (WorkflowServices)
  • 引导进程(冷启动)
  • 动态加载和卸载特定业务应用程序域 (business AppDomain) 中的服务
  • 发布/订阅服务(热启动)- 可选

这一层使服务能够虚拟化到存储在企业网络任何位置的元数据中,称为企业服务存储库 (Enterprise Service Repository)。本文重点介绍这一层,它允许基于 WCF/WF 可扩展性模型构建更复杂的松耦合服务,例如插入消息检查器、参数验证器等。

附加的文章实现允许在独立应用程序和/或企业应用程序中使用此解决方案。以下代码片段仅显示实现的重要部分。详细描述企业服务总线 (Enterprise Service Bus) 解决方案的设计和实现超出了本文的范围。我认为,虚拟服务 (VirtualService) 可以是一个很好的入门组件,可以帮助您将思路转向 ESB 架构作为元数据驱动模型。

好的,让我们进入虚拟服务 (VirtualService) 元数据的描述。

元数据

构建由元数据驱动的架构模型需要不同的编程方式。关于配置数据与元数据的论坛讨论仍在继续。从配置数据过渡到模型驱动的关键点在哪里?我们如何管理数据,我们是否有合适的建模元数据的工具?其中一个答案(模型驱动应用程序)是本文,其中配置数据(引导端点)指向元数据;换句话说,在特定应用程序的范围内,引导数据是配置数据,而应用程序元数据是数据,代表了上下文驱动业务模型中可管理的可控制流。当然,如果视图的范围扩大(企业应用程序),引导配置数据也可以是元数据的一部分。

因此,下图显示了虚拟服务 (VirtualService) 元类的类图(数据契约)。

在此设计中,元数据资源代表 XML 格式文本中的资源正文或指向资源的标识符,因此属性的类型是字符串。如前所述,ESB 中可以有两种类型的元数据,此设计与工具生成的物理元数据基于逻辑模型(逻辑元数据)相关。引导/加载服务是物理元数据的本地处理器,其职责是创建和启动特定应用程序域 (AppDomain) 中的服务(包括创建不存在的域)。

虚拟服务 (VirtualService) 需要以下资源:

配置

配置元数据由system.ServiceModel 部分表示,就像在应用程序配置文件中使用的一样。注意,此配置应仅与一个特定服务相关。此资源的内容(XML 格式文本)可以由 Microsoft 服务配置编辑器实用工具创建。默认值是空字符串,表示从应用程序配置文件中使用此资源。

配置元数据是插入行为和绑定端点的扩展、工作流运行时服务以及任何自定义对象的地点。

XOML 和规则

XOML 和规则元数据是虚拟服务 (VirtualService) 中工作流模型所需的。XOML 代表操作契约的过程,具有由数据驱动的特定规则。此资源的 XML 格式文本由工作流设计器(Visual Studio/WF 的一部分)作为建模过程的一部分生成。对于没有工作流或类型激活工作流的虚拟服务 (VirtualService),默认值是空字符串。

WSDL

这是描述服务元数据的标准文档。对于由虚拟服务 (VirtualService) 生成 WSDL 文档,默认值是空字符串。在这种情况下,当虚拟服务 (VirtualService) 基于不同的服务操作进行复合(虚拟化)时,建模工具负责将此文档作为服务元数据的一部分来创建。

XSLT

XSLT 元数据是服务中介器所需的,用于将输入/输出/错误消息转换(中介)为正确的格式。在此设计中,已创建自定义ServiceMediators 部分来声明中介器基于System.Xml.Xsl.XslCompiledTransform 类请求的数据。默认值是空字符串,表示不需要中介。注意,操作中介是虚拟服务 (VirtualService) 的一项强大功能,其中参数和自定义函数可以从运行时转换的服务传递。

以下示例显示了一个serviceMediators 部分。

<serviceMediators>
 <operations>
  <operation action='test2'> 
   <input> 
    <mediator name='m1' xpath='/' validation='true'>
     <xslt>
      <xsl:stylesheet version='1.0' 
         xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
         xmlns:ext='urn:function1'
         xmlns:x='urn:test'>
       <xsl:output method='xml' omit-xml-declaration='yes'/>
       <xsl:param name='param1'>defaultValue</xsl:param>
       <xsl:template match='x:Person'>
        <Person >
         <Name id='{$param1}'>
          <xsl:value-of select='x:FirstName'/>
          <xsl:text>/</xsl:text>
          <xsl:value-of select='ext:Name(x:FirstName, x:LastName)'/>
          <xsl:text>/</xsl:text>
          <xsl:value-of select='x:LastName'/>
         </Name>
        </Person>
       </xsl:template>
      </xsl:stylesheet>
     </xslt>
     <params>
      <param name='param1' namespace='' value='12345' />
     </params>
     <functions>
      <function name='urn:function1' type='RKiss.ESB.XsltExtensionObject, ESB'/>
     </functions>
    </mediator> 
    </input>
    <output>
     <mediator name='m2' xpath='/' xslt='c:\repository\translator.xsl'/>
    </output> 
    <error/>
   </operation> 
  </operations> 
</serviceMediators>

以上是关于虚拟服务 (VirtualService) 元数据设计的第一个部分。下一步是找到 ServiceHost 和 WorkflowServiceHost 如何由直接资源或间接从文件系统驱动。实现的最简单方法是重写ApplyConfiguration 方法。

重写 ApplyConfiguration

以下代码片段显示了此重写的实现,其中服务配置的处理基于 XML 格式的文本,并且配置文件位于文件系统的任何文件夹中。

protected override void ApplyConfiguration()
{
  string config = (string)CallContext.GetData("_config");
  string configurationName = this.Description.ConfigurationName;

  if (config != null && config.TrimStart().StartsWith("<"))
  {
    ServiceModelSection model = 
      ServiceModelConfigHelper.DeserializeSection<ServiceModelSection>(config);
          
    // validate model
    // ...
        
    // add behavior based on the config metadata
    ServiceModelConfigHelper.AddBehaviors(this, model);

    ServiceElement se = model.Services.Services[configurationName];
    base.LoadConfigurationSection(se);

    // add custom binding based on the config metadata
    ServiceModelConfigHelper.AddCustomBinding(this, model);

    return;
  }
  else
  {
    ServiceElement se = 
      ServiceModelConfigHelper.GetServiceElement(configurationName, config);
    base.LoadConfigurationSection(se);
  }             
}

注意,基类虚拟ApplyConfiguration 方法在构造函数中调用,因此 Config 元数据通过CallContext 线程槽传递。如您所见,重写实现简短明了,使用了ServiceModelConfigHelper 实用工具,该工具基于system.ServiceModel 部分组创建自己的ServiceModelSection

让我们看看这些部分:

ServiceModelSection

ServiceModelSection 对象使我们能够使用XmlReader 反序列化一个节。以下代码片段显示了它的实现,其中system.ServiceModel 的每个元素都通过反射进行反序列化。

public sealed class ServiceModelSection : ConfigurationSection
{
  public ServicesSection Services { get; set; }
  public ClientSection Client { get; set; }
  public BehaviorsSection Behaviors { get; set; }
  public BindingsSection Bindings { get; set; }
  public ExtensionsSection Extensions { get; set; }
  public DiagnosticSection Diagnostics { get; set; }
    
  protected override void DeserializeSection(XmlReader reader)
  {
    Type cfgType = typeof(ConfigurationSection);
    MethodInfo mi = cfgType.GetMethod("DeserializeElement",
          BindingFlags.Instance | BindingFlags.NonPublic);

    reader.ReadStartElement("configuration");
    while (reader.NodeType != XmlNodeType.EndElement)
    {
       if (reader.Name == "system.serviceModel")
       {
         reader.ReadStartElement();
         while (reader.NodeType != XmlNodeType.EndElement)
         {
           #region sections
           if (reader.IsEmptyElement || 
              reader.NodeType == XmlNodeType.Whitespace)
           {
              reader.Skip();
              continue;
           }

           if (reader.Name == "diagnostics")
           {
             Diagnostics = new DiagnosticSection();
             mi.Invoke(Diagnostics, new object[]{reader, false});
             reader.ReadEndElement();
           }
           else if (reader.Name == "extensions")
           {
             Extensions = new ExtensionsSection();
             mi.Invoke(Extensions, new object[]{reader,false});
             reader.ReadEndElement();
           }
           else if (reader.Name == "services")
           {
             Services = new ServicesSection();
             mi.Invoke(Services, new object[]{reader,false});
             reader.ReadEndElement();
           }
           else if (reader.Name == "bindings")
           {
             Bindings = new BindingsSection();
             mi.Invoke(Bindings, new object[]{reader,false});
             reader.ReadEndElement();
           }
           else if (reader.Name == "behaviors")
           {
             Behaviors = new BehaviorsSection();
             mi.Invoke(Behaviors, new object[]{reader,false});
             reader.ReadEndElement();
           }
           else if (reader.Name == "client")
           {
             Client = new ClientSection();
             mi.Invoke(Client, new object[] { reader, false });
             reader.ReadEndElement();
           }
           #endregion
           reader.MoveToContent();
          }
        }
        reader.Skip();
        reader.MoveToContent();
        }
        reader.ReadEndElement();
    }
}

ServiceModelConfigHelper

ServiceModelConfigHelper 是一个静态辅助类,用于隐藏一些通过反射访问私有配置类的实现。以下代码片段显示了一个通用的静态方法,用于反序列化位于文件系统或 XML 格式资源中的配置节。

public static T DeserializeSection<T>(string config) where T : class
{
  T cfgSection = Activator.CreateInstance<T>();
  byte[] buffer = new ASCIIEncoding().GetBytes(config);
  XmlReaderSettings xmlReaderSettings = new XmlReaderSettings();
  xmlReaderSettings.ConformanceLevel = ConformanceLevel.Fragment;

  using (MemoryStream ms = new MemoryStream(buffer))
  {
    using (XmlReader reader = XmlReader.Create(ms, xmlReaderSettings))
    {
      Type cfgType = typeof(ConfigurationSection);
      
      MethodInfo mi = cfgType.GetMethod("DeserializeSection", 
          BindingFlags.Instance | BindingFlags.NonPublic);
          
      mi.Invoke(cfgSection, new object[] { reader });
    }
  }
  return cfgSection;
}

下一个代码片段显示了GetServiceElement 的实现,其中由其名称指定的服务可以从所需配置文件中位于ServiceModelSectionGroup 的服务进行反序列化。

public static ServiceElement GetServiceElement(
    string configurationName, string configFilename)
{
  ExeConfigurationFileMap filemap = new ExeConfigurationFileMap();
  
  filemap.ExeConfigFilename = string.IsNullOrEmpty(configFilename) ? 
    AppDomain.CurrentDomain.SetupInformation.ConfigurationFile : 
    Path.GetFullPath(configFilename);
      
  Configuration config = 
    ConfigurationManager.OpenMappedExeConfiguration(filemap,
        ConfigurationUserLevel.None);
      
  ServiceModelSectionGroup serviceModel = 
    ServiceModelSectionGroup.GetSectionGroup(config);

  foreach (ServiceElement se in serviceModel.Services.Services)
  {
    if (se.Name == configurationName)
    {
       return se;
    }
  }
  throw new ArgumentException(" ... ");
}

LocalRepositorySection

LocalRepositorySection 代表了 Bootstrap/Loader 服务的自定义配置节。此节位于应用程序配置文件中,可用作预定义元数据的低级存储库。以下代码片段显示了其结构。

<LocalRepository enable="true" endpointName="boot">
  <HostMetadata hostName="Test" assemblyNames="WorkflowLibrary1; WorkflowLibrary2">
    <Services>
      <add name="Host.Test" appDomainHostName="test" />
      <add name="WorkflowLibrary1.Workflow6" appDomainHostName="*" />
      <add name="WorkflowLibrary1.Workflow4" appDomainHostName="*" 
        config="..\..\ESB\LocalRepository\Metadata\Workflow40.config" 
        xoml="..\..\ESB\LocalRepository\Metadata\WorkflowLibrary1.Workflow4.xoml"
        rules="..\..\ESB\LocalRepository\Metadata\WorkflowLibrary1.Workflow4.rules"/>
      <add name="WorkflowLibrary1.Workflow1" appDomainHostName="123" 
         config="Test_Workflow.exe.config" 
        wsdl="Workflow1.wsdl"
        xslt="Workflow1.xslt" />                   
    </Services>
  </HostMetadata>
</LocalRepository>

如您所见,上面的部分声明了目录中每个服务的元数据。LocalRepository 部分有两个属性。这些声明的属性根据端点声明远程下载元数据。例如:

<client>
  <endpoint name="boot" 
      address="net.pipe:///LocalRepository" 
      binding="netNamedPipeBinding" 
      contract="RKiss.ESB.IBootstrap"/>
</client>

好的,现在我们有了支持由元数据驱动的虚拟服务 (VirtualService) 模型的所有要素。我们需要一个服务/操作来遍历元数据,并按照它们在目录中的位置顺序创建所有请求的虚拟服务 (VirtualServices)。此服务仅在启动过程(冷启动)期间运行,因此我们称之为 Bootstrap。

Bootstrap 通过在启动过程的正确位置调用一个“关键方法”来激活。以下代码片段显示了这一行:

HostServices.Current.Boot(m_HostMetadata);

其中,m_HostMetadata 是一个可选参数,用于将嵌入式元数据传递到程序集中。注意,此元数据优先级最低,并且会被配置文件中的元数据或远程接收的元数据覆盖。

Bootstrap/Loader 启动服务是虚拟服务 (VirtualServices) 的一项强大功能。其实现在程序集大小非常小,但能够加载任何复杂的服务解决方案到BOOTSTRAP.LOADER.AppDOMAINS 中。将HostServices.Current.Boot 整合到应用程序进程的启动顺序中,您的应用程序就可以成为元数据驱动的逻辑模型的一部分。

Bootstrap

Bootstrap 是一个拉取服务,用于根据元数据资源创建和初始化虚拟服务 (VirtualServices)。HostMetadata 代表了由物理模型描述的虚拟服务 (VirtualServices) 的目录,例如应用程序名称、域、端点、绑定、XOML、规则、XSLT 等。注意,Bootstrap 不需要了解逻辑模型以及谁/什么创建了元数据。Bootstrap 所需的唯一信息是获取元数据的端点。

以下代码片段显示了 Bootstrap 的ServiceContract

[ServiceContract]
public interface IBootstrap
{
  [OperationContract]
  HostMetadata GetMetadata(
    string hostName,     // logical name of the application
    string appName,     // application name (default appDomainName)
    string machineName,    // netbios machine name 
    string serviceName    // option: all services or specific service (IIS7/WAS)
    );       
}

Repository 服务保存物理模型的元数据(或知道其位置),并将返回由 HostMetadata 目录组织的元数据。基于此目录,Bootstrap 将创建(如果不存在)一个应用程序域 (AppDomain) 主机,并加载一个虚拟服务 (VirtualService),可选地加载 XOML 工作流;请参见下图。

Bootstrap 拉取服务是 ESB 基础设施的一部分,必须在默认应用程序域 (default AppDomain) 中创建。如前所述,逻辑应用程序模型可以映射到由业务应用程序域 (business AppDomains) 驱动的物理模型。上图显示了两个存储:一个是存储所有已创建和加载的应用程序域 (AppDomains) 的HostServices,另一个是业务应用程序域 (business AppDomain) 中用于保存虚拟服务 (VirtualServices) 引用的ServiceHostActivator。注意,这些存储是线程安全的,并且位于数据槽中。

HostServices

HostServices 是在特定应用程序域 (AppDomains) 中托管虚拟服务 (VirtualServices) 的主要对象之一。它是一个线程安全的控制器,用于管理应用程序域 (AppDomains) 的启动、加载、中止、打开等。下图显示了HostServices 的类图。

HostServices 对象在默认应用程序域 (default AppDomain) 中初始化,并且其引用存储在域数据槽中。对该对象的首次调用将创建其实例;请参见以下代码片段。

public static HostServices Current
{
  get
  {
    string key = typeof(HostServices).FullName;
    HostServices hostservices = AppDomain.CurrentDomain.GetData(key) as HostServices;
    if (hostservices == null)
    {
      lock (AppDomain.CurrentDomain.FriendlyName)
      {
        hostservices = AppDomain.CurrentDomain.GetData(key) as HostServices;
        if (hostservices == null)
        {
          hostservices = new HostServices();
          AppDomain.CurrentDomain.SetData(key, hostservices);
        }
      }
    }
    return hostservices;
  }
}

HostServices 也用作引导DomainLoader 服务的一部分,作为 Bootstrap 启动服务的一部分。此服务允许远程控制HostServices 存储,例如在特定应用程序域 (AppDomain) 中加载、卸载和重新加载虚拟服务 (VirtualServices)。这是虚拟服务 (VirtualService) 基础设施的一项强大功能,其中逻辑模型可以根据业务需求以隔离的方式控制物理部署。

因此,引导目录元数据应包含一个服务,即DomainLoader,请参见以下配置片段示例。

<LocalRepository enable="true" endpointName="boot">
  <HostMetadata hostName="Application_Test" >
    <Services>
      <add name="RKiss.ESB.DomainLoader" />
    </Services>
  </HostMetadata>
</LocalRepository>

注意,默认应用程序域 (default AppDomain) 无法通过此服务管理,因此建议将其加载到默认应用程序域 (default AppDomain) 中。当然,可以加载自定义应用程序域 (AppDomain) 来管理其他应用程序域 (AppDomains),但内置的 Bootstrap/Loader 是允许在进程中管理域的最低服务。

以下代码片段显示了HostServices 方法的示例,以查看实现样板代码。

public void Add(string appDomainName, ServiceConfigData config, 
       Stream workflowDef, Stream rulesDef)
{
  try
  {
    _rwl.AcquireWriterLock(TimeSpan.FromSeconds(60));
    
    appDomainName = ValidateAppDomainName(appDomainName);
    AppDomain appDomain = this.CreateDomainHost(appDomainName);   
    ServiceHostActivator.Create(appDomain, config, workflowDef, rulesDef);
  }
  finally
  {
    _rwl.ReleaseWriterLock();
  }
}

如上代码片段所示,有一个ServiceHostActivate 对象用于处理在特定应用程序域 (AppDomain) 中托管虚拟服务(在此方法中是 XOML 激活的工作流服务)的所有魔术工作。让我们看看这个魔术对象的内部。

ServiceHostActivator

ServiceHostActivator 是一个远程对象,用于处理应用程序域 (AppDomain) 中的托管服务。注意,Remoting 是在同一进程的域之间进行通信的正确方式,因此此对象派生自MarshalByRefObject 类。一旦虚拟服务 (VirtualService) 被加载到应用程序域 (AppDomain) 中,其ServiceHostActivator 就会存储在数据槽存储中以供管理,例如打开、关闭和中止。

下图显示了ServiceHostActivator 对象的类图。

在非默认应用程序域 (non-default AppDomain) 中托管服务的概念基于在目标应用程序域 (AppDomain) 中初始化远程ServiceHostActivator 对象,然后调用其方法来处理目标域中的业务。以下代码片段显示了其Create 方法的实现。

public static ServiceHostActivator Create(AppDomain appDomain, 
       ServiceConfigData config, Stream workflowDef, Stream rulesDef)
{
  string _assemblyName = Assembly.GetAssembly(typeof(ServiceHostActivator)).FullName;
  string typeName = typeof(ServiceHostActivator).ToString();
  
  // get proxy
  ServiceHostActivator activator = 
   appDomain.CreateInstanceAndUnwrap(_assemblyName, typeName) as ServiceHostActivator;
  
  // remoting call
  activator.SetHost(config, workflowDef, rulesDef);
  
  return activator;
}

以下片段显示了用于托管虚拟服务 (VirtualService) 的远程处理方法的实现。

private void SetHost(ServiceConfigData config,
        Stream workflowDef, Stream rulesDef, string baseAddresses)
{
  try
  {
    if (_host == null)
    {
      // workaround for passing file/string to the override ApplyConfiguration
      CallContext.SetData("_config", config.Config);

      _host = new WorkflowServiceHostESB(config, workflowDef, rulesDef, 
          BaseAddresses(baseAddresses));
          
      _host.Faulted += new EventHandler(_host_Faulted);
      
      this.AddToStorage(this);
    }
    else
    {
      throw new InvalidOperationException("The ServiceHost already exists");
    }
  }
  finally
  {
    CallContext.FreeNamedDataSlot("_config");
  }
}

以上是关于基于元数据将虚拟服务 (VirtualServices) 托管(引导)到主机进程中的内容。让我们继续讨论虚拟服务 (VirtualService) 设计和概念的另一个重要部分:消息中介。消息可以在 WCF(通过扩展)和/或工作流模型中进行中介。我将重点介绍 XOML 激活的工作流服务中的消息中介,它为逻辑模型提供了更多的虚拟化和重用性。

中介器 (Mediator)

中介器 (Mediator) 是消息流中可插入的组件,用于以透明的方式处理集成服务与其消费者。中介器 (Mediator) 是一个一对一的消息处理器。中介器处理器可以为每个特定转换进行硬编码,或者灵活地由 XML 格式文本表示的元数据驱动,类似于 XSLT。它是逻辑模型中一个非常强大的组件,消费者和生产者在此逻辑上连接。虚拟服务 (VirtualService) 在 XOML 激活的工作流中使用中介器 (Mediator) 来处理消息转换,但从概念上看,任何服务或代理本质上都是中介器 (Mediator)。例如,事件源可以通过中介器 (Mediator) 发布事件兴趣,其中事件兴趣被转换为事件消息,然后根据订阅元数据将消息传递给事件订阅者。

将消息中介(生成、分派、转换等)封装到工作流活动中,使我们能够在设计时编排虚拟服务 (VirtualService)。如前所述,有两种中介器:

紧耦合中介器

紧耦合中介器通常是为特定的、已知的消息转换而构建的。下图显示了用于 Create 操作的 WS-Transfer 中介器的示例。

在此示例中,CreateProcessor 是一个自定义复合活动,用于根据 WS-ResourceTransfer 协议规范,在中介两个业务层之间的连接性,这两个业务层被 WS-Transfer 封装。其子活动不需要了解此协议的细节,它们的编排基于业务需求。上图显示了一个将消息转换为不同连接性(契约和传输)的示例,如我们示例中的 Ping2 操作。注意,中介器可以处理已类型化和未类型化的请求/应答消息。请参见 WSTransferActivity 项目中的详细实现,其中实现了 Create/Get/Put/Delete 活动(中介器)。

松耦合中介器

松耦合中介器基于消息(或消息的一部分)到另一条消息或其部分的消息转换,使用 XSLT 处理器。仅为此功能,可以在服务操作契约中使用未类型化消息。松耦合中介器的设计分为两部分。第一部分是MediatorActivity,这是一个自定义复合(顺序)活动,包含消息中介的活动。通过将MediatorActivity 插入消息流和元数据,我们获得了在逻辑模型中编排其过程的能力。

下图显示了自定义MediatorActivity

MediatorActivity 负责根据消息操作和模式提取元数据。在与运行时操作匹配的情况下,将执行顺序活动,否则消息将绕过输入到输出(无中介)。以下代码片段显示了重写的Execute 方法中的实现。

protected override ActivityExecutionStatus Execute(ActivityExecutionContext context)
{
  if (executionContext == null)
    throw new ArgumentNullException("context");

  // bypass status
  ActivityExecutionStatus status = ActivityExecutionStatus.Closed;

  // pre-processing
  this.OnInvoking(EventArgs.Empty);
  base.RaiseEvent(Mediator.InvokingEvent, this, EventArgs.Empty);

  // action
  if (MediatorMode == MediatorMode.Custom)
  {
    status = base.Execute(context);
  }
  else if (MediatorMode != MediatorMode.None && MessageInput != null)
  {
    if (Mediators == null && OperationContext.Current != null)
    {
      ServiceConfigDataService service =
       OperationContext.Current.Host.Extensions.Find<ServiceConfigDataService>();
      
      if (service != null)
      {
        string xslt = service.ServiceConfigData.Xslt;
        Mediators = ServiceMediatorsSection.GetMediators(xslt, 
            MessageInput.Headers.Action, MediatorMode);
      }
    }
    if (Mediators != null)
    {
      status = base.Execute(context);
    }
    else 
    {
      using(MessageBuffer buffer = MessageInput.CreateBufferedCopy(int.MaxValue))
      {
        MessageOutput = buffer.CreateMessage();
      }
    }
  }   
  else
  {
    using(MessageBuffer buffer = MessageInput.CreateBufferedCopy(int.MaxValue))
    {
       MessageOutput = buffer.CreateMessage();
    }  
  }  
     
  // post-processing
  this.OnInvoked(EventArgs.Empty);
  base.RaiseEvent(Mediator.InvokedEvent, this, EventArgs.Empty);
  return status;
}

中介器设计第二部分是实际处理消息转换的地方。为此,设计了自定义XsltProcessorActivity 活动,使用了非常有用的类System.Xml.Xsl.XslCompiledTransform 来处理转换。这个“关键”类需要加载由 XSLT 格式文本、源以及可选参数和函数列表表示的元数据来定制 XSLT 处理。转换结果是生成输出流。在当前实现中,结果必须始终是一个根消息(Envelope)。

下图显示了自定义活动XsltProcessor 和元数据示例。

<operation action='http://tempuri.org/ITest3/Ping3'> 
 <output> 
  <mediator name='abcd' xpath='/' validation='false'>
   <xslt>
    <xsl:stylesheet version='1.0' 
     xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
     xmlns:a='http://www.w3.org/2005/08/addressing'
     xmlns:s='http://www.w3.org/2003/05/soap-envelope'>
     <xsl:output method='xml' omit-xml-declaration='yes' />
     <xsl:param name='result'>?</xsl:param>     
     <xsl:template match='/'>
       <s:Envelope>
        <s:Header>
          <a:Action s:mustUnderstand='1'>
            http://tempuri.org/ITest3/Ping3Response
          </a:Action>
        </s:Header>
        <s:Body>
          <Ping3Response xmlns='http://tempuri.org/'>
            <Ping3Result>
              <xsl:value-of select='$result'/>
            </Ping3Result>
          </Ping3Response>
         </s:Body>
       </s:Envelope>
     </xsl:template>    
    </xsl:stylesheet>
   </xslt>
   <params>
     <param name='result' namespace='' value='Hello World'/>
   </params> 
   <funcions />
  </mediator> 
 </output>
</operation>

中介器 (Mediator) 是服务(或客户端)虚拟化的一项强大组件,可以在不进行编码或重新编译应用程序的情况下即时进行消息中介。例如,上面的XsltProcessor 属性(如ParametersMediatorsMediatorIndexMediatorName)允许在运行时根据业务需求定制 XSLT 处理器。请参见元数据中的参数result。它可以根据元数据或 XOML 通过Parameters 属性进行配置;请参见以下示例。

<ns2:Mediator x:Name="Mediator1" 
         MediatorMode="Output" 
         MessageInput="{ActivityBind Workflow6,Path=_message}" 
         MessageOutput="{ActivityBind Workflow6,Path=_returnValue}">
  <ns2:XslProcessor x:Name="XslProcessor1" 
         Mediators="{ActivityBind /Parent,Path=Mediators}"
         MediatorName="om3" 
         MessageInput="{ActivityBind /Parent,Path=MessageInput}" 
         MessageOutput="{ActivityBind xslProcessor2,Path=MessageInput}">
       <ns2:XslProcessor.Parameters>
         <WorkflowParameterBinding ParameterName="result">
           <WorkflowParameterBinding.Value>
             <ActivityBind Name="/Parent" Path="MessageInput.Headers.Action"/>
           </WorkflowParameterBinding.Value>
         </WorkflowParameterBinding>
       </ns2:XslProcessor.Parameters>
  </ns2:XslProcessor>
</ns2:Mediator>

中介器 (Mediator) 以松耦合的方式复合了业务逻辑以用于集成过程。从消息处理的角度来看,存在以下主要情况:

  • 消息将在虚拟服务 (VirtualService) 中终止,无论是否响应消费者。
  • 消息将转发给服务,无论是否响应消费者。
  • 消息将被存储并可靠地转发给服务。
  • 消息将触发控制流状态机中的一个状态。

虚拟服务 (VirtualService) 中的消息可以集成任何已类型化和未类型化消息的组合。通常,客户端生成已类型化的消息。此外,业务服务具有类型驱动的操作,但它们的中间服务可以根据需要更改消息类型,例如:已类型化 => 未类型化 => 未类型化 => 已类型化消息。注意,与 CLR 硬编码类型相比,中介器 (Mediator) 更倾向于使用未类型化的消息进行 XSLT 转换。

以下示例显示了一个用于发送通知消息的活动。消息在其业务处理(Operation1)之后,根据订阅和事件兴趣进行中介,并发送以通知其他服务。

让我们更详细地描述一下如何使用虚拟服务 (VirtualService) 进行通知。

用于发布/订阅通知的虚拟服务 (VirtualService)

发布/订阅通知基于将事件消息传递给订阅者。有几种方式可以传递此消息。有关更多详细信息,请参阅我的文章:WCF 的 WS-Eventing。本文重点介绍推送通知模型,其中订阅(如事件兴趣)由订阅服务本身持久化。这种模式可以简化基于网络广播的消息传递。WCF 具有一个非常强大的内置绑定,可用于我们的概念,使用点对点网络技术,如 netPeerTcpBinding。

发布/订阅通知使用单向操作契约和未类型化消息,如下面的代码片段所示。

[ServiceContract(Namespace = "urn:rkiss.esb", 
 SessionMode = SessionMode.NotAllowed)]
public interface IBroadcast
{
    [OperationContract(IsOneWay = true, Action = "*")]           
    void ProcessMessage(Message message);
}

注意,这是对未知、可断开连接的订阅者的广播消息,因此,根据业务场景,发布者(其通道)负责根据业务知识(逻辑元数据模型)处理消息到目标的传递。另一方面,在 ESB 的概念中,在网络上广播消息是一项非常强大的功能。它允许简化逻辑模型到物理本地存储库的部署,尤其是在集群环境中。

虚拟服务 (VirtualService) 可以通过元数据(如带有地址扩展的端点)启用订阅通知服务。下图显示了一个具有发布/订阅功能的 ESB 连接器,该功能由虚拟服务 (VirtualService) 虚拟化。

正如您在端点管道中所看到的,我们有一个自定义扩展来处理服务订阅。此过滤器是服务中的一个端点行为扩展,其实现非常直接且轻量级。请参见以下代码片段。

class FilteringEndpointBehavior : IEndpointBehavior
{
  MessageFilter _filter = null;
  string _xpath = string.Empty;
  bool _enable = true;

  public FilteringEndpointBehavior(string xpath, bool enable)
  {
    this._xpath = xpath;
    this._enable = enable;
  }
  
  public FilteringEndpointBehavior(MessageFilter filter, bool enable)
  {
    this._filter = filter;
    this._enable = enable;
  }
 
  public void ApplyClientBehavior(ServiceEndpoint endpoint, 
     ClientRuntime clientRuntime)
  {
    throw new InvalidOperationException("This behavior is not supported");
  }
  
  public void ApplyDispatchBehavior(ServiceEndpoint endpoint, 
       EndpointDispatcher endpointDispatcher)
  {
   if (this._enable)
   {
     endpointDispatcher.AddressFilter = this._filter != null ? 
      this._filter : 
      new XPathMessageFilter(this._xpath, new XPathMessageContext());
   }
  }
  
  public void AddBindingParameters(ServiceEndpoint endpoint, 
         BindingParameterCollection bindingParameters){}
  public void Validate(ServiceEndpoint endpoint){}

}

ApplyDispatchBehavior 中,我们的过滤器逻辑使用基于 XPath 格式文本或其他自定义MessageFilterXPathMessageFilter。为了将此扩展注入到正确的端点,我们需要一个管道类,如以下代码片段所示。

public class FilteringEndpointBehaviorExtension : BehaviorExtensionElement
{
  public const string ExtensionName = "filter";

  protected override object CreateBehavior()
  {
    return new FilteringEndpointBehavior(XPath, Enable);
  }

  public override Type BehaviorType
  {
    get { return typeof(FilteringEndpointBehavior); }
  }

  [ConfigurationProperty("xpath", DefaultValue = "", IsRequired = true)]
  [StringValidator(MaxLength = 1024)]
  public string XPath
  {
    get { return (string)base["xpath"]; }
    set { base["xpath"] = value; }
  }

  [ConfigurationProperty("enable", DefaultValue = true, IsRequired = false)]
  public bool Enable
  {
    get { return (bool)base["enable"]; }
    set { base["enable"] = value; }
  }
}

现在,我们已经支持通过元数据订阅服务,所以让我们为订阅服务制作一些元数据。以下代码片段显示了端点。

<endpoint 
  address='net.p2p://broadcastMesh/servicemodelsamples/announcements'
  binding='netPeerTcpBinding' 
  bindingConfiguration='xpathAddressFilter' 
  contract='RKiss.ESB.IBroadcast'>
</endpoint>

以下代码显示了如何基于 XPath 属性自定义过滤器。在此示例中,订阅服务仅接受来自地址以“ga2”结尾的发布者的通知。

<endpointBehaviors>
 <behavior name='xpathAddressFilter'>
   <filter xpath="/s12:Envelope/s12:Header/wsa10:To
        [contains(substring-after(.,'net.pipe:///'), 'ga2')]" />
   </behavior>
</endpointBehaviors>

实际上,我们可以创建任何基于头和/或正文(业务)的过滤器 XPath。这个主题驱动的虚拟服务 (VirtualService) 可以简化 ESB 上的业务处理,其中服务可以根据主题(事件兴趣)附加或分离到企业网络中的任何位置。

为了更好地了解发布/订阅 XOML 声明,下图显示了三个简单的示例。第一个示例代表一个用于更新本地业务资源的订阅服务,就像典型的接收器服务一样。第二个示例显示了事件消息的发布者。xsltProcessor 活动用于根据业务需求进行消息中介。

最后一个示例(数字 3)显示了一个业务场景,其中接收到的消息被中介并转发给业务服务。其响应将生成(发布)一个通知消息以触发业务工作流的另一部分。

正如您所见,上图描述了一个 XOML 激活的顺序工作流,但在发布/订阅通知模型中,状态机工作流对于处理由业务事件驱动的长时间运行的业务流程更加健壮。换句话说,我们可以有一个具有状态机的订阅服务来处理特定主题驱动的状态,例如:下订单、信用验证、库存、发货、退货等,以进行业务管理。

注意,在上述地址过滤示例中,虚拟服务 (VirtualService) 能够拦截消息进行检查、验证参数等。这是 WCF 范例中伟大扩展的一部分。

托管虚拟服务 (VirtualServices)

虚拟服务 (VirtualService) 可以由 IIS、Windows 进程激活服务 (WAS)、Windows 服务或自托管的托管应用程序托管。它们之间主要区别在于服务如何在应用程序域 (AppDomain) 中激活。IIS/WAS 托管允许基于第一个传入请求/消息激活服务,而自托管则要求在服务使用之前必须对其进行托管。

基本上,虚拟服务 (VirtualServices) 将由 Windows 服务应用程序托管,用于消息中介、路由、复合操作等,作为 ESB 的连接器。创建用于托管虚拟服务 (VirtualServices) 的 Windows 服务非常直接。以下代码片段是一个如何进行自托管的示例。

protected override void OnStart(string[] args)
{
    HostServices.Current.Boot();
}

protected override void OnStop()
{
    HostServices.Current.Close();
}

当然,Bootstrap/Loader 操作需要一个正确的配置文件;例如,以下代码会将虚拟服务 (VirtualServices) 引导到应用程序域 (AppDomains) 中:

<LocalRepository enable="true" endpointName="boot">
  <HostMetadata hostName="Application_Test">
    <Services/>
  </HostMetadata>
</LocalRepository>

<system.serviceModel>
  <client>
    <endpoint name="boot" 
      address="net.pipe:///LocalRepository" 
      binding="netNamedPipeBinding" 
      bindingConfiguration="bootstrap" 
      contract="RKiss.ESB.IBootstrap" />
  </client>
  <bindings>
    <netNamedPipeBinding>
      <binding name="bootstrap" >
        <readerQuotas maxStringContentLength="1000000" /> 
        <security mode="None"/>
      </binding>
    </netNamedPipeBinding>
  </bindings>
</system.serviceModel>

注意,更改配置文件不会回收进程;因此,我们可以利用此功能重新加载应用程序域 (AppDomain) 以执行配置更改。

使用上述自托管封装了 ESB 和业务服务之间的连接性。它们通常托管在 IIS/WAS 进程中。在 IIS 7/WAS 中使用虚拟服务 (VirtualService) 进行业务逻辑的情况下,已实现以下工厂来简化基于元数据的激活。注意,该工厂只能处理一个特定的虚拟服务 (VirtualService)。

public class VirtualServiceFactory : ServiceHostFactoryBase
{
  public override ServiceHostBase CreateServiceHost(string constructorString,
       Uri[] baseAddresses)
  {
    ServiceHostBase service = null;

    if (string.IsNullOrEmpty(constructorString))
         throw new ArgumentException(constructorString);

    string serviceName = constructorString.Trim(new char[] { ' ', '"' });

    using (HostServices services = new HostServices())
    {
      HostMetadata catalog = services.CreateCatalog(serviceName, null);

      if (catalog.Count == 0)
        throw new ArgumentOutOfRangeException("Missing metadata");

      // only one service is supported
      if (catalog.Count != 1)
         throw new ArgumentOutOfRangeException("Only one service is required");

      // only service for current appDomain is supported
      string appDomainHostName = catalog[0].AppDomainHostName;
      
      if (string.IsNullOrEmpty(appDomainHostName) || 
          appDomainHostName == "*" || appDomainHostName.ToLower() == "default")
      {
        // create service
        services.CreateServices(catalog);
        
        // hosted service
        service = services.GetHostedService(serviceName, true);
     }
      else
      {
        throw new ArgumentOutOfRangeException("AppdDomainHostName");
      }
    }
    return service;
  }
}

IIS7/WAS 服务器中的管道激活基于 SVC 文件,其中声明了服务名称和工厂;请参见以下示例,其中自定义工厂将从存储库引导特定的服务。

<%@ServiceHost 
    Language="C#" 
    Debug="true" 
    Service="WorkflowLibrary1.Workflow6" 
    Factory="RKiss.ESB.VirtualServiceFactory"%>

最后,我们可以得到一个典型的虚拟服务 (VirtualServices) 部署图,其中集成预/后处理由 Windows 服务托管,业务部分推送到负载均衡的进程(如 IIS 和 WAS)。这两个虚拟服务 (VirtualServices) 都通过 ESB 从存储库服务 (Repository Service) 引导。

以上是对虚拟服务 (VirtualService) 设计及其实现的简要描述。让我们继续测试虚拟服务 (VirtualService) 模型的一些描述功能。

测试

在编译附加源代码之前,请确保您的计算机上已安装以下内容:

  • Visual Studio 2008
  • 计算机上的 P2P 网络(仅适用于 Win2003/XPSP2)- 可选,用于测试
  • .NET Framework 3.5
  • 工作流 SQL 脚本
  • IIS 虚拟目录用于https:///VirtualService 测试项目(这是一个选项)。

源代码(解压后)将在以下文件夹中安装解决方案:

解决方案分为三个顶级文件夹,其中虚拟服务 (VirtualService) 的实际实现位于 ESB 文件夹中。

测试部分被隔离在一个单独的文件夹中,其中有更多项目依赖于托管、监视、工作流等。

最后一个顶级文件夹Logs 用于存储 WCF 跟踪消息,以便进行详细的故障排除。

成功编译后,我们可以开始测试。基本上,我们需要三个进程,例如虚拟服务 (VirtualServices) 的主机进程、客户端和测试器(作为元数据的本地存储库)。注意,完整的测试需要运行 Vista(P2P 广播)、IIS7/WAS 上的测试,并安装 VirtualServiceHost 服务。当然,所有测试都必须以管理员身份运行。

那么,让我们做一个简单的测试。

步骤 1 - 引导

启动测试器程序,然后启动主机控制台程序。您可以看到虚拟服务 (VirtualServices) 的启动过程。根据应用程序域 (AppDomains) 进行加载和打开。

步骤 2 - GetHostedServices

按下GetHostedServices 按钮,DomainLoader 服务(位于Host.exe 域中)将发送一个列表,其中包含Host.exe 进程中所有已托管(可管理)的虚拟服务 (VirtualServices)。

步骤 3 - 添加虚拟服务 (VirtualService)

输入所选虚拟服务 (VirtualService) 的应用程序域 (AppDomain) 名称;例如,NewBusiness,然后按Load 按钮。加载过程可以在主机控制台屏幕上监视。注意,应用程序域 (AppDomain) 名称和元数据已实现验证。如果您再次单击Load 按钮,错误窗口将显示原因消息。

步骤 4 - 使用虚拟服务 (VirtualService)

在步骤 2 中,名为 Workflow7 的虚拟服务 (VirtualService) 已添加到新的应用程序域 (AppDomain) 并打开。因此,按下Event 按钮,事件消息将通过 P2P 通道广播,并带有特定的上下文头。

使用虚拟服务 (VirtualService) 的循环次数可以通过 1 到 100 范围内的上下计数器进行设置。您可以在此操作期间试验 Unload 和 Load Domain 功能。注意错误消息。

此步骤中的另一个测试是运行来自WCF_WF_CardSpace_Samples 库的 Microsoft P2P 通道示例 - 路径\WCF\WCF_WF_CardSpace_Samples\WCF\Basic\Binding\Net\PeerTcp\Default\CS

步骤 5 - 更多消费者(客户端)

启动客户端控制台程序以进行负载测试。在此负载测试期间,可以加载或卸载其他域。注意,Reload 按钮硬编码用于域Business_1,其中加载了Host.TestWorkflow6 服务。当然,这些服务可以单独卸载并加载到另一个应用程序域 (AppDomain) 中,并且一切都会继续正常工作。

步骤 6 - 复合服务(WS-Transfer、Ping 等)

这是 Workflow6 虚拟服务 (VirtualService) 的测试,其中操作是从不同契约复合而来的。我在 ESB 代码中包含了最新 WS-ResourceTransfer 规范的实现。WSTransferActivity 可以将业务逻辑与此协议解耦。通过按下 WS_Transfer 按钮,测试器将向 Workflow6 虚拟服务 (VirtualService) 生成一个 Create 消息。

要从虚拟服务 (VirtualService) Workflow6 获取服务元数据,请按/mex 按钮。注意,WSDL 文档是从 LocalRepository 获取的,在此示例中,它是一个伪造的文档。如前所述,设计工具负责根据复合操作创建正确的 WSDL 文档。

步骤 7 - 在 IIS7 中托管

此测试要求在按下XYZ 按钮之前,在 IIS7 上安装http:/localhost/VirtualService。一旦按下按钮,IIS 的第一个请求将加载虚拟服务 (VirtualService)(在本例中为 Worfklow6)。注意,IIS 将根据.svc 文件中的声明单独管理所有虚拟服务 (VirtualServices)。

步骤 8 - 在 WindowsService 中托管

此步骤需要关闭主机控制台程序(我们不能有重复的端口)并安装 VirtualServiceHost 服务。服务启动后,Bootstrap 将向测试程序中托管的 LocalRepository 请求元数据。一旦引导了 DomainLoader 服务,我们就可以根据业务需求加载其他虚拟服务 (VirtualServices)。

以上是内置测试功能的全部内容。您可以查看 Test 文件夹实现并添加更多自定义测试,例如更改 Tester 或 Host 中的元数据、端点、传输等。

最终说明:本文所述的虚拟服务 (VirtualService) 实现不是生产版本。该实现缺少一些功能,例如错误处理、日志记录消息、安全性和策略处理的可插入组件、P2P 通道扩展等。本文重点关注由元数据模型驱动的服务虚拟化及其在业务集成过程中的位置。

结论

本文旨在展示工作流服务在基于元数据的集成过程中的使用。XOML 激活的工作流使该集成过程能够在设计阶段作为逻辑元数据驱动模型的一部分来创建。正如您在本文中所见,虚拟服务 (VirtualService) 的概念很大程度上基于元数据。如果没有合适的工具,就很难对业务连接性和集成进行故障排除。说到我的观点,构建更多工具对每个产品来说都是一项巨大的投资,尤其是在产品由元数据驱动的情况下。当然,会有额外的开发成本,但通过使用一些通用的元数据定义描述,如 XAML、XOML、WCF 服务模型等,我们可以为公司节省预算。

参考文献

© . All rights reserved.