Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / load-testing

VirtualService for ESB

4.90/5 (32 votes)
19 Feb 2008CPOL37 min read 1   1.2K  
This article describes the design, implementation, and usage of VirtualService for the Enterprise Service Bus, using the Microsoft .NET FX 3.5 technology.

Contents

Features

  • Loosely coupled design pattern
  • WorkflowService virtualization without coding
  • Model driven design
  • WCF connectivity
  • WF orchestration (typed and untyped messages)
  • Built-in Bootstrap/Loader service
  • Hosting services based on business AppDomains
  • Metadata of the config, WSDL, XOML, rules and XSLT stored in the repository
  • Sync and Async connectivity fashions
  • Publish/Subscribe capability
  • Message mediation based on the XSLT metadada
  • Built-in WS-Resource Transfer mediators
  • Capability to plug-into the Enterprise Service Bus
  • WCF and WF Extensibility support
  • IIS7/WAS and self hosting
  • .NET FX 3.5 technology

Introduction and Concept

Microsoft recently released a version of the .NET FX 3.5, where they introduced a new context driven service model called as the WorkflowService. This model is based on the Windows Communication Foundation (WCF) and the Windows Workflow Foundation (WF) technologies. It represents a logical model of the connectivity to expose a workflow. The service operation in the workflow is abstracted into the new context driven activity such as ReceiveActivity based on the Contract-First or Workflow-First declaration.

The business connected layers represented by the client (channel) or new context driven activity (SendActivity) can consume a Workflow Service transparently like another standard service in a sync or async manner with a variant of the Message Exchange Patterns (MEP) such as Input/Output, Request/Reply, Session, Duplex, etc., over different physical transports such as TCP, HTTP, MSMG, pipe, etc. It is a great open connectivity paradigm with the ability to customize and extend all elements based on the business needs.

Besides that, the WorkflowService enables to orchestrate a service operation. This common logical model of the service connectivity and operation can be created declaratively based on the metadata, using the Model-First tool. This is a great feature of the Workflow Service model, when metadata is stored in the knowledge base of the services and workflows (called as resource repository), allowing to create and initiate the connectivity and operation in the specific AppDomain.

Let's continue in our service abstraction. As I mentioned earlier, service operations are represented by a sequential or state machine workflow model. From the connectivity point of view, the WCF model is described by the metadata of the connectivity such as Address, Binding, Contract (ABC), and behavior. The WF model is described by the workflow definition declared by the XOML metadata or the CLR type.

Encapsulating an actual business service operation from the connectivity and message mediation into the pre-processing, processing, and post-processing activities, the WorkflowService can represent a VirtualService for mediation to consume the business layer transparently in a loosely coupled manner.

The following picture shows the WorkflowService connected to the ESB:

Image 1

The WorkflowService, as the above picture shows, is represented by a resource stored in the Metadata to declare the WCF and WF models. The workflow is activated by XOML/rules metadata for Pre/Post Processing and invoking an actual business service.

Note, the workflow orchestration is focusing on the virtualization of the service operation as a part of the logical model. Its business logic drives an integration process. There are many kinds of orchestrations based on message mediation, dispatching, rerouting, business virtualization, etc. Also, the XOML and rules are parts of the metadata created by the design time and stored in the knowledge base (service repository), and they don't orchestrate an actual business process, just pre-processing and post-processing and how to invoke the business process. That's the strategy and concept of the VirtualService driven by the model and represented by the metadata.

So, now we know what is behind the VirtualService implemented by the WorkflowService model. The following picture shows its symbol used in this article. Note, there is no limit to the VirtualServices in the application; it is independent from the business model.

Image 2

The VirtualService lives in the AppDomain where it can access business objects, services, and workflows. The following picture shows an example of the VirtualServices in the logical layer located on the top of the private business layer within the same AppDomain:

Image 3

Of course, there is no boundary limitation between the VirtualService and the business layer, services, or individual service operations. The VirtualService can compose (virtualize) different service operations based on the business model needs.

The following picture shows this example, where a front-end tier publishes a portion of the private business services located behind the internal firewall:

Image 4

Notice, the VirtualService doesn't require to build and recompile changes in the solution, because its model is based on the metadata, and hosting has a pre-built boot service agent. The above picture shows a virtualization of the service operations from the private business services (WCF, Web Services, Remoting, etc.).

During design time, the modeling tool will create a WSDL/mex document based on the private services as a part of the VirtualService metadata for responding back to the client. The model driven architecture is a logically centralized enterprise solution in the metadata located in the repository, and physically decentralized based on the deployment schema. The modeling is a significant part of the design, simulation, and deployment of the application across the enterprise network. Without proper tools, it will be very difficult to handle and manage more complex solutions, for instance, endpoints, message mediations, XOML, rules, etc.

OK, back to hosting a VirtualService in the AppDomain. Mapping the business model to the physical model allows us to group and isolate a logical business into physical layers represented by AppDomains. This model strategy is very important in the Enterprise Architecture for reliability and manageability purposes.

The following picture shows an Application process with separate business AppDomains:

Image 5

As the above picture shows, the Application process will automatically create a Default AppDomain with a special built-in ESB Service Agent. This agent has a responsibility to create and manage a specific AppDomain for hosting the VirtualServices based on the metadata. Note, modeling will create all necessary metadata for runtime Bootstrap/Loader action such as name of the AppDomain, identity, etc.

This is a great feature of the VirtualService, where the model can decide its hosting based on the business needs. The VirtualServices in the Business AppDomain can be loaded and reloaded on the fly in the specific order, without interrupting other non-related businesses.

ESB and VirtualService

Consuming a VirtualService is fully transparent and abstracted by the metadata where it is declared "who and how" can talk to the service and operation. This is a different approach of programming style, known as declarative programming, where a solution is divided into pre-built common infrastructure components driven by metadata, allowing to manage a solution within the logical model.

The following picture shows a logical connectivity between the service and its consumer using a manageable infrastructure - Enterprise Service Bus:

Image 6

As you can see in the above picture, the Enterprise Service Bus represents an implementation of the Logical Connectivity between the business layers. Mapping the business model to the physical one is produced by Tools during the design/modeling time. The result of the modeling is metadata stored in the Repository. To understand this metadata at runtime, the ESB has pre-built common infrastructure (components) such as Adapter, Connector, Bootstrap/Loader, etc.

The following picture shows a Model Driven ESB Strategy:

Image 7

During the deployment time, the business model described by the metadata in the Enterprise Service Repository (ESR) is deployed to the LocalRepository based on the deployment schema. Note, the LocalRepository represents a cache of the real-time metadata for the Bootstrap/Loader service:

Image 8

The Enterprise Service Repository contains the logical model of the enterprise solutions, where VirtualServices are logically connected to the Enterprise Service Bus via the Connector. The Connector is represented in the repository as a resource of the VirtualService metadata.

The following picture shows a logical position of the VirtualService as a Connector for connecting a Business Service to the ESB:

Image 9

As you can see, the VirtualService is a mediator for the Business Service. The VirtualService is driven by metadata such as XOML, rules, XSLT, etc., to mediate a message and forward it to the next destination such as a private (or public) service or ESB. The following picture shows a scenario where the VirtualService has an embedded Adapter to the ESB:

Image 10

Of course, the VirtualService can also be used for message mediation, like shown in the following picture:

Image 11

That's all for describing a position of the VirtualService and metadata in the model of the ESB. Let's go back to our VirtualService.

As I mentioned earlier, each VirtualService must be declared in the repository prior to the usage of the Bootstrap/Loader. There are two possible levels of the repository. There are local and enterprise levels. The local repository holds metadata mapping to the physical deployment schema for each application topic. This metadata is requested by the Bootstrap start-up service in the runtime application. The local repository can be managed at the physical level using the MMC console tool.

The following picture shows a position of the VirtualService as a facade service for private business services:

Image 12

The local repository can be stored in the application assembly, config file, file system, or database. Using the local repository enables us to use VirtualServices as a part of the private central application configuration.

The following picture shows an example of the config file where the LocalRepository section describes the metadata for the Bootstrap/Loader:

Image 13

As you can see, there is an endpointName attribute to query metadata remotely in the LocalRepository section. Basically, the VirtualService can also be configured locally without any consideration for usage within the ESB model. Of course, in this case, the VirtualService is managed individually within the specific application. On the other hand, having the ESB.DomainLoader in the local repository will enable our application to be hosted by the VirtualServices any time in a fully transparent manner.

VirtualService mediation

The concept of the VirtualService is based on the WorkflowService model. Basically, there are two places of service mediations. The first one is based on WCF extensibility using a custom extension for binding, service behavior, operation, message, and parameter inspections. This kind of mediation is provided by injecting a strong type mediator into the input and/or output pipeline. The plug-in place for those mediators is the system.serviceModel section of the configuration file. The Microsoft SvcConfigEditor tool can be used to handle this section.

The second place of the service/operation mediations is an XOML workflow. This mediation opens many capabilities of the VirtualService, besides that, we already have a tool to orchestrate workflow and rules properly generating its metadata (XOML, rules). There is no restriction for the XOML workflow in the VirtualService; it can completely be a business service solution, but from the design pattern of the view, this XOML should handle pre/invoke/post processing without the actual business processor. Based on that, we can say that, the VirtualService XOML is responsible for mediation of the operation (message and exchange pattern) and encapsulation of the business processor from the ESB plumbing.

The following picture shows an encapsulation of the business processor (located in a separate sequential workflow) from the message mediation represented by custom activities such as Transformer_IN and _OUT:

Image 14

The following picture shows an example of the XOML as a part of the VirtualService metadata.

Image 15

Message Exchange Pattern mediation

Using the new ReceiveActivity in the VirtualService enables to abstract a service operation in the VirtualService. This composite activity allows processing an incoming service message based on the specified message exchange pattern. For instance, the Request/Reply pattern will request to return a value back to the caller, opposite to the One-Way Output pattern.

The following picture shows how it can be changed using MEP in the scenario where the caller needs to receive a confirmation return value only, but the actual business process will be processed after that in a Publish/Subscribe fashion:

Image 16

Now, let's focus on the VirtualService operation contract. Based on the WCF model, the VirtualService can be orchestrated for service operation with typed or untyped messages.

Typed Operation

The operation contract with a typed message allows receiving and handling direct operation parameters based on the method signature. This type of operation is suitable for pre-built services with well known service operations such as WS-* (WS-Eventing, WS-ResourceTransfer, etc.). The VirtualService contract is mapped to the XOML workflow using the ListenActivity with branches of the EventDrivenActivity based on the number of operations.

The following picture shows an example of XOML for two operation contracts. Each branch of the EventDrivenActivity has one ReceiveActivity for a specific operation contract.

Image 17

An example of the XOML for a WS-ResourceTransfer:

Image 18

As you can see in the above example, the VirtualService can handle four operations of the WS_Transfer contract. Each operation has its own custom composite activity to handle the message mediation (see colored activity within the ReceiveActivity) and invoking of the business processor.

Another example, VirtualService for Resource Storage on the ESB driven by WS-ResourceTransfer contract:

Image 19

Untyped Operation

Operation contract with an untyped message allows more flexibility and virtualization in the VirtualService than a typed operation. The concept of using an untyped operation in the VirtualService is based on creating a generic contract which will be valid for any typed and untyped consumer in a fully transparent manner. In other words, we want to pass a raw message to the ReceiveActivity for its processing based on the modeling.

The following code snippet is an example of a service contract with an untyped operation:

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

The incoming raw message received by the ReceiveActivity can be selected based on the Action header to the correct branch, as shown in the following example of the XOML template:

Image 20

Untyped operations give us the capability to create a compound service with different operations (WSDL) in already existing services, placing message mediators, changing a message exchange pattern, etc. The service mediation can be done during the modeling time as a result of generating metadata for Bootstrap/Loader service.

The following picture shows an example of XOML, where an incoming raw message is validated in the Policy activity and then, based on the Action value, is passed to a specific branch for processing. There are many different possible operations, including an operation /mex to return a WSDL document, for this VirtualService driven by an untyped message; see the following example:

Image 21

VirtualService with an XOML StateMachine

Basically, the VirtualServices are declared for XOML sequential workflows (see all of the examples), but there is no restriction to use it for state machine workflow definitions as well. In this case, each state will receive an event message (typed or untyped) like in the sequential workflow. The advantage of using a state machine in the VirtualService is to simplify a control process for event driven, long running business processes, where a context business conversation needs to flow via more states, for instance: order, validation, payment, shipping, etc.

The following picture shows an example of the VirtualService, where the service mediation is orchestrated by the state machine:

Image 22

WCF Service (no XOML)

As I mentioned earlier, the VirtualService is based on the WorkflowService model. In some cases (routers, dispatchers, publishers, ...), the WCF Service model can be also used for virtualization of the operation contracts. In these cases, the message can only be mediated using the service extension components against the flexibility of the XOML workflow.

The following picture shows the VirtualService based on the WCF Service:

Image 23

That's all for the introduction of the VirtualService in the scope of the Enterprise Service Bus and local usage.

I assume that you have the knowledge of the WCF and WF technologies and their extensibility; therefore, I will focus more on the design and implementation of the bootstrapping VirtualServices, which is only a small portion, but a very fundamental component of the ESB design and implementation.

OK, let's start with the Design, following up with the implementation.

Design and Implementation

The concept of the VirtualService is based on a metadata driven model. The loosely coupled WCF/WF configuration model is represented by the data stored in the system.serviceModel section group that allows using it only in the application config file. There is no direct .NET FX built-in support to configure another source of the metadata such as database, file system, etc.

The design and implementation of the VirtualService needs to solve the following layer on top of the WCF/WF model :

  • service metadata structure (WSDL, XOML, rules, XSLT, endpoints, binding, behavior, workflow, extensions, ...)
  • hosting metadata structure (process, AppDomain, ...)
  • hosting services and WorkflowServices based on the metadata
  • bootstrapping process (cold boot)
  • dynamically loading and unloading services in the specific business AppDomain
  • Publish/Subscribe service (hot boot) - optional

This layer enables a virtualization of the service into the metadata stored as a resource anywhere on the Enterprise network known as the Enterprise Service Repository. This article is focused on that layer which will allow to build more compound services in a loosely coupled manner based on the WCF/WF extensibility model, such as plug-in a message inspector, parameter validator, etc.

The attached article implementation allows using this solution in standalone and/or enterprise applications. The following code snippets will only show significant parts of the implementation. It is out of the scope of this article to describe more details about the design and implementation of the Enterprise Service Bus solution. I think, the VirtualService can be a good start up component to move your thinking to the ESB architecture as a metadata driven model.

OK, let's move to the VirtualService metadata description:

Metadata

Building an architecture model driven by metadata requires a different way of programming. Forum discussions about Configuration data vs. Metadata will still continue. Where is the critical point to move from configuration data to model driven? How can we manage data, do we have the right tools for modeling metadata, etc. One of the answers for these questions (model-driven application) is this article, where a configuration data (bootstrap endpoint) is pointing to metadata; in other words, in the scope of the particular application, the bootstrap data is configuration data as against application metadata where data represents a manageable control flow in the context driven business model. Of course, if the scope of the view goes up (enterprise applications), the bootstrap config data can also be a part of the metadata.

So, the following picture shows a class diagram (data contract) of the VirtualService metadata:

Image 24

In this design, the metadata resource represents either the body of the resource in the XML formatted text or identifier to the resource, therefore the type of the property is a string. As I mentioned earlier, there can be two kinds of metadata in the ESB, this design is related to the physical metadata generated by tool based on the logical model (logical metadata). The Bootstrap/Loader service is the local processor of the physical metadata, and its responsibility is to create and initiate a service in the specific AppDomain (included creating a domain if it doesn't exist).

The following resources are required by the VirtualService:

Config

The Config metadata is represented by the system.ServiceModel section like it is used in the application config file. Note, this config should be related only for one specific service. Contents for this resource (XML formatted text) can be created by the Microsoft Service Configuration Editor utility. The default value is an empty string for using this resource from the application config file.

The Config metadata is the place to plug-in the extensions of the behavior and binding endpoints, workflow runtime service, and any custom object.

XOML and Rules

The XOML and Rules metadata are required by the workflow model in the VirtualService. The XOML represents a process of the operation contract with specific rules driven by the data. The XML formatted text for this resource is generated by the workflow designer (part of the Visual Studio/WF) as a part of the modeling process. The default value is an empty string for a VirtualService without the workflow or for a type activated workflow.

WSDL

This is a standard document for describing the service metadata. The default value is an empty string for generating WSDL documents by the VirtualService. In this case, where the VirtualService is compounding (virtualizing) service based on different service operations, the modeling tool has a responsibility to create this document as a part of the service metadata.

XSLT

The XSLT metadata is required by service mediators to translate (mediate) an input/output/error message to the proper format. In this design, the custom ServiceMediators sections has been created to declare a data requested by the mediator based on the System.Xml.Xsl.XslCompiledTransform class. The default value is an empty string for no mediation required. Note, the operation mediation is a very powerful feature of the VirtualService, where the parameters and custom functions can be passed from a service on the runtime transformation.

The following example shows a serviceMediators section:

XML
<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>

That's all for the first part of the design; having the metadata for the VirtualService. The next step is to find how the ServiceHost and WorkflowServiceHost can be driven by direct resources or indirectly from the file system. The easy way of implementation is to override the ApplyConfiguration method.

Overriding ApplyConfiguration

The following code snippet shows an implementation of this override, where handling a service configuration is based on the XML formatted text, and the config file is located in any folder of the file system.

C#
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);
  }             
}

Note, the base virtual ApplyConfiguration method is invoked in the constructor , therefore the Config metadata is passed via the CallContext thread slot. As you can see, the override implementation is short and straightforward using the ServiceModelConfigHelper utility that creates its own ServiceModelSection based on the system.ServiceModel group.

Let's look at these parts:

ServiceModelSection

The ServiceModelSection object enables us to deserialize a section using the XmlReader. The following code snippet shows its implementation, where each element of the system.ServiceModel is deserialized via Reflection:

C#
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

The ServiceModelConfigHelper is a static helper class to hide some implementation using a reflection access to the private configuration classes. The following code snippet shows a generic static method to deserialize a configuration section located in the file system or in the XML formatted resource:

C#
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;
}

The next code snippet shows an implementation of the GetServiceElement where a service specified by its name can be deserialized from the ServiceModelSectionGroup located in the required config file:

C#
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

The LocalRepositorySection represents a custom configuration section for the Bootstrap/Loader service. This section is located in the application config file, and it can be used as a low level repository of the pre-defined metadata. The following code snippet shows its structure:

XML
<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>

As you can see, the above section declares a metadata for each service in the catalog. There are two attributes in the LocalRepository section. These declared attributes download metadata remotely, based on the endpoint declaration. For example:

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

OK, now we have all the support for the VirtualService model driven by metadata. We need a service/action to walk through the metadata and create all the requested VirtualServices in the order of their position in the catalog. This service will only run during the start up process (cold boot), that's why we call it the Bootstrap.

The Bootstrap is activated by calling one "horse method" in the right place of the start-up process. The following code snippet shows this line:

C#
HostServices.Current.Boot(m_HostMetadata);

where, m_HostMetadata is an optional parameter to pass embedded metadata into the assembly. Note, this metadata has the lowest priority, and can be overwritten by metadata located in the config file or by remotely received metadata.

The Bootstrap/Loader start-up service is a very powerful feature of VirtualServices. Its implementation has a very small assembly size, but a high capability to load any complex service solution in BOOTSTRAP.LOADER.AppDOMAINS. Incorporating the HostServices.Current.Boot in the application process start-up sequence, your application can be a part of the logical model driven by metadata.

Bootstrap

The Bootstrap is a pull-up service to create and initialize VirtualServices based on the metadata resource. The HostMetadata represents a catalog of VirtualServices described by a physical model such as application name, domains, endpoints, bindings, XOML, rules, XSLT, etc. Note, the Bootstrap doesn't need to know anything about the logical model and what/who created the metadata. The one and only single information that is required by the Bootstrap is an endpoint to get the metadata.

The following code snippet shows a ServiceContract for Bootstrap:

C#
[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)
    );       
}

The Repository service holds the metadata of the physical model (or knows where it is) and will send back a metadata organized by the HostMetadata catalog. Based on this catalog, the Bootstrap will create (if doesn't exist) an AppDomain host and loads a VirtualService with or without an XOML workflow; see the following picture:

Image 25

The Bootstrap pull-up service is a part of the ESB infrastructure, and must be created in the default AppDomain. As I mentioned earlier, the logical application model can be mapped into the physical model driven by business AppDomains. The above picture shows two storages such as the storage (HostServices) of all created and loaded AppDomains and storage in the business AppDomain for holding references about the VirtualServices (ServiceHostActivator). Note, the storages are thread safe and are located in the data slot.

HostServices

The HostServices is one of the major objects for hosting VirtualServices in the specific AppDomains. It is a thread safe controller for managing AppDomains such as boot, load, abort, open, etc. The following picture shows a class diagram of the HostServices:

Image 26

The HostServices object is initiated in the default AppDomain, and its reference is stored in the domain data slot. The first call to this object will create its instance; see the following code snippet:

C#
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;
  }
}

The HostServices is also used for booting the DomainLoader service as part of the Bootstrap start up service. This service enables to control the HostServices storage remotely, such as loading, unloading, and reloading VirtualServices in the specific AppDomain. This is a great feature of the VirtualService infrastructure, where a logical model can control a physical deployment based on the business needs in an isolated manner.

Therefore, the boot catalog metadata should contain one less service such as DomainLoader, see the following config snippet example:

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

Note, the default AppDomain is not manageable via this service, therefore it is recommended to load it into the default AppDomain. Of course, it is possible to load a custom AppDomain to manage other AppDomains, but the built-in Bootstrap/Loader is a minimum service that allows managing domains in the process.

The following code snippet shows an example of the HostServices method to see the implementation boilerplate:

C#
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();
  }
}

As you can see in the above code snippet, there is an object ServiceHostActivate to handle all the magic work for hosting a VirtualService (in this method is an XOML activated WorkflowService) in the specific AppDomain. Let's look at the insides of this magic object.

ServiceHostActivator

The ServiceHostActivator is a remote object to handle a hosting service in the AppDomain. Note, Remoting is the right way to communicate between domains in the same process, therefore this object is derived from the MarshalByRefObject class. Once, the VirtualService has been loaded into the AppDomain, its ServiceHostActivator is stored in the data slot storage for managing purposes such as open, close, and abort.

The following picture shows a class diagram of the ServiceHostActivator object:

Image 27

The concept of the hosting service in a non-default AppDomain is based on initiating a remote ServiceHostActivator object in the target AppDomain and then invoking its method for processing business in the target domain. The following code snippet shows how its Create method is implemented:

C#
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;
}

and the following snippet shows the implementation of the remoting method for hosting a VirtualService:

C#
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");
  }
}

That's all for hosting (booting) the VirtualServices into the host process based on the metadata. Let's move to another important part of the VirtualService design and concept, the message mediation. The message can be mediated in WCF (via extensions) and/or Workflow models. I will focus on message mediation in the XOML activated WorkflowService, which gives us more virtualization and reusability in the logical model.

Mediator

The Mediator is a pluggable component in the message flow to process integration services with their consumers in a transparent manner. The mediator is a one-to-one message processor. The mediator processor can be hard-coded for each specific transformation, or flexibly driven by metadata represented by XML formatted text similar to XSLT. It is a very powerful component in the logical model where the consumer and producer are logically connected. The VirtualService uses a mediator in the XOML activated workflow to process a message transformation, but from the conceptual point of view, every service or proxy is basically a mediator. For instance, the event source can publish an event interest via a mediator where the event interest is transformed to the event message and the message is delivered to the event subscriber based on the subscription metadata.

Encapsulating a message mediation (generation, dispatching, transformation, etc.) into the workflow activity enables us orchestrate our VirtualService during design time. As I mentioned earlier, there are two kinds of mediators:

Tightly coupled mediator

The tightly coupled mediator is usually built for specific well-known message transformation. The following picture shows an example of the WS-Transfer mediator for the Create operation:

Image 28

In this example, the CreateProcessor is a custom composite activity to mediate a connectivity between two business layers encapsulated by a WS-Transfer based on the WS-ResourceTransfer protocol specification. Its child activities do not need to know about the details of this protocol, their orchestration is based on the business needs. The above picture shows an example of converting a message to a different connectivity (contract and transport) such as in our example the Ping2 operation. Note, the mediator can handle the typed and untyped Request/Response messages. See the detail implementation in the WSTransferActivity project, where the Create/Get/Put/Delete activities (mediators) are implemented.

Loosely coupled mediator

The loosely coupled mediator is based on the message (or part of the message) transformation to another message or its parts using the XSLT processor. For this feature only, an untyped message can be used in the service operation contract. The design of the loosely coupled mediator is decoupled into two parts. The first one is the MediatorActivity as a custom composite (sequential) activity of the activities for message mediation. By plugging the MediatorActivity into the message flow and metadata, we gain capability to orchestrate its process mediation in the logical model.

The following picture shows the custom MediatorActivity:

Image 29

The MediatorActivity has a responsibility to extract the metadata based on the message action and mode. In the case of matching with runtime operation, the sequential activity is going to be executed, otherwise the message is bypassed from the input to the output (no mediation). The following code snippet shows this implementation in the overridden Execute method.

C#
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;
}

The second part of the Mediator design is actually a place where we can process a message transformation. For this purpose, the custom XsltProcessorActivity activity has been designed using a very useful class, System.Xml.Xsl.XslCompiledTransform for processing of the transformation. This "horse" class requires to load metadata represented by the XSLT formatted text, source, and an optional list of parameters and functions to customize the XSLT processing. As a result of the transformation, the output stream is generated. In the current implementation, the result must always be a root message (Envelope).

The following picture shows a custom activity XsltProcessor and an example of the metadata:

Image 30

XML
<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 is a very powerful component for service (or client) virtualization to enable a message mediation on the fly without coding or recompiling the application. For instance, the above XsltProcessor properties such as Parameters, Mediators, MediatorIndex, and MediatorName allow to customize an XSLT processor during runtime based on the business needs. See the parameter result in the metadata. It can be configured in the metadata or XOML via the Parameters property; see the following example:

XML
<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>

The Mediator composites a business logic for integration process in a loosely coupled manner. From the message processing point of view, there are the following major cases:

  • The message will terminate in the VirtualService with or without the response to the consumer
  • The message will be forwarded to the service with or without the response to the consumer
  • The message will be stored and forwarded to the service in a reliable manner
  • The message will trigger a state in the control flow state machine

The message within the VirtualService can integrate any combination of typed and untyped messages. Usually, the client generates a typed message. In addition, the business service has typed driven operations, but their intermediate services can change the message type based on the need, such as typed=>untyped=>untyped=>typed message. Note, the mediator prefers an untyped message for its XSLT transformation, compared to the CLR hard-coded type.

The following example shows an activity for sending a notification message. The message, after its business processing in Operation1, is mediated based on the subscription and event interest and sent to notify other services:

Image 31

Let's describe more details about using the VirtualService for notifications.

VirtualService for Publish/Subscribe Notification

The Publish/Subscribe notification is based on delivering an event message to the subscribers. There are a few ways of how this message can be delivered. For more details, see my article: WS-Eventing for WCF. In this article, I focus on the push notification model, where a subscription (such as an event interest) is persisted by the Subscribe service itself. This pattern fashion allows simplifying a message delivery based on network broadcasting. WCF has a very powerful built-in binding for our concept, using a pier-to-pier network technology such as netPeerTcpBinding.

The Publish/Subscribe notification is uses an One-Way operation contract with an untyped message, like it is shown in the following code snippet:

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

Note, this is a broadcasting message to the unknown, disconnectable subscribers; therefore, based on the business scenario, the publisher (its channel) has a responsibility to handle the delivery of the message to the destinations based on the business knowledge (logical metadata model). On the other hand, broadcasting a message across the network is a very powerful feature in the concept of the ESB. It allows simplifying the deployment of the logical model to the physical local repositories, especially in a clustered environment.

The VirtualService can be enabled for Subscribe notification service based on the metadata, such as an endpoint with address extensions. The following picture shows an ESB Connector with Publish/Subscribe features virtualized by the VirtualService:

Image 32

As you can see in the endpoint pipeline, we have a custom extension to handle the service subscription. This filter is an endpoint behavior extension in the service, its implementation is very straightforward and lightweight. Please see the following code snippet:

C#
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){}

}

In the ApplyDispatchBehavior, our filter logic uses the XPathMessageFilter based on the XPath formatted text or another custom MessageFilter. To inject this extension into the right endpoint based on the metadata, we need a plumber class such as in the following code snippet:

C#
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; }
  }
}

Now, we have all the support for subscribing the service by the metadata, so let's make some metadata for the Subscribe service. The following code snippet shows the endpoint:

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

The following code shows customizing a filter based on the XPath property. In this example, the Subscribe service will accept a notification only for a Publisher with an address ended by 'ga2'.

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

Practically, we can create any filter XPath for incoming messages based on the headers and/or body (business). This topic driven VirtualService can simplify business processing on the ESB, where the service can be attached or detached anywhere on the enterprise network based on the topic (event interest).

To get a better picture of the Publish/Subscribe XOML declaration, the following picture shows three simple examples. The first example represents a Subscribe service for updating a local business resource, like a typical sink service. The second example shows the Publisher of the event message. The xsltProcessor activity is for mediation of the message based on the business needs.

The last example (number 3) shows a business case, where the received message is mediated and forwarded to the business service. Its response will generate (Publish) a notification message to trigger another part of the business workflow:

Image 33

As you can see, the above picture describes an XOML activated sequential workflow, but in the Publish/Subscribe notification model, the state machine workflow is more robust to handle long running business processes driven by business events. In other words, we can have one Subscribe service with a state machine for specific topic driven states; for instance, place order, credit validation, inventory, shipping, return, etc. to handle business management.

Note, in the above described address filtering example, the VirtualService has a capability to intercept a message for inspection, validating parameters, etc. It is part of the great extensions in the WCF paradigm.

Hosting VirtualServices

The VirtualService can be hosted by IIS, Windows process Activation Service (WAS), a Windows Service, or by self-hosted managed applications. The major difference between them is how the service is activated in the AppDomain. The IIS/WAS hosting allows to activate a service based on the first incoming request/message, as against self-hosting, where the service must be hosted prior to using.

Basically, the VirtualServices will be hosted by the Windows Service Application for message mediation purposes, routing, compounding operations, etc. as a Connector to the ESB. Creating a Windows Service for hosting VirtualServices is straightforward. The following code snippet is an example of how self-hosting can be done:

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

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

Of course, a proper config file is required by the Bootstrap/Loader action; for example, the following code will boot the VirtualServices into the AppDomains::

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

<system.serviceModel>
  <client>
    <endpoint name="boot" 
      address="net.pipe://localhost/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>

Note, changing a config file will not recycle a process; therefore, we can use this feature for reloading an AppDomain to perform configuration changes.

Using the above self-hosting encapsulates a connectivity between the ESB and Business Services. They are usually hosted in the IIS/WAS processes. In the case of using the VirtualService for business logic in the IIS 7/WAS, the following factory has been implemented to simplify its activation based on the metadata. Note, the factory can only handle one specific VirtualService:

C#
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;
  }
}

The plumbing activation in the IIS7/WAS server is based on the SVC file, where the service name and factory are declared; see the following example, where the custom factory will boot a specific service from the repository.

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

Finally, we can have a typical picture of the deployment for VirtualServices, where the integration pre/post process is hosted by a Windows Service and the business part is pushed to the load balanced processes such as IIS and WAS. Both VirtualServices are booting from the Repository Service via the ESB.

Image 34

That is all for briefly describing the VirtualService design and its implementation. Let's continue with testing some of the described features of the VirtualService model.

Test

Prior to compiling the attached source, be sure the following installations are on your machine:

  • Visual Studio 2008
  • p2p networking on your machine (only for Win2003/XPSP2) - optional for testing
  • .NET Framework 3.5
  • Workflow SQL scripts
  • IIS Virtual Directory for the http://localhost/VirtualService test project (this is an option)

The source code (after unzipping) will install the ssolution in the following folders:

Image 35

The solution is divided into three top folders, where the actual implementation of the VirtualService is located in the ESB folder:

Image 36

The testing part is isolated in a separate folder where more projects are dependent on hosting, monitoring, workflows, etc.

Image 37

The last top folder Logs is created for storing WCF trace messages for detailed troubleshooting.

After successful compiling, we can start our testing. Basically, we need three processes such as a Host process for VirtualServices, a Client, and a Tester (as a Local Repository of the metadata). Note, the full test will require to run the test on Vista (p2p broadcasting), IIS7/WAS, and with an installation of the VirtualServiceHost service. Of course, for all of the testing, all processes must run as Administrator.

So, let's make a simple test.

Step 1 - Bootstrapping

Launch the the Tester program following the Host console program. You can see a boot process of the VirtualServices. Loading and opening based on the AppDomains:

Image 38

Step 2 - GetHostedServices

By pressing the GetHostedServicess button, the DomainLoader service (located in the Host.exe domain) will send a list of all hosted (manageable) VirtualServices from the Host.exe process:

Image 39

Step 3 - Adding a VirtualService

Type the name of the AppDomain for the selected VirtualService; for example, NewBusiness, and press the Load button. The loading process can be monitored on the Host console screen. Note, a validation is implemented for the AppDomain name and metadata. If you click the Load button again, the error window will show you a reason message.

Image 40

Step 4 - Consuming VirtualService

In step 2, a VirtualService with the name Workflow7 was added to the new AppDomain and also opened. Therefore, by pressing the Event button, the event message will be broadcasted via a p2p channel with a specific context header.

Image 41

The number of loops for consuming a VirtualService can be setup by the down/up counter in the range of 1 to 100. You can experiment with the Unload and Load domain features during this action. Watch the error messages.

Another test in this step is to run a Microsoft sample for the p2p channel from the WCF_WF_CardSpace_Samples gallery - path \WCF\WCF_WF_CardSpace_Samples\WCF\Basic\Binding\Net\PeerTcp\Default\CS.

Step 5 - More consumers (clients)

Launch the Client console program to make some load testing. During this load test, other domains can be loaded or unloaded. Note, the Reload button is hard-coded for the domain Business_1 where the Host.Test and Workflow6 services are loaded. Of course, these services can be unloaded and loaded individually to another AppDomain, and everything will continue to work correctly.

Step 6 - Compound service (WS-Transfer, Ping, ...)

This is a test of the Workflow6 virtual service, where operations are compounded from different contracts. I included the implementation of the latest WS-ResourceTransfer specification in the ESB code. The WSTransferActivity can decouple the business logic from this protocol. By pressing the WS_Transfer button, the Tester will generate a Create message to the Workflow6 VirtualService.

To get the service metadata from the VirtualService Workflow6, press the button /mex. Note, the WSDL document is obtained from the LocalRepository, and in this example, it is a fake document. As I mentioned earlier, the design tool is responsible for creating the correct WSDL document based on the compounded operations.

Step 7 - Hosting in IIS7

This test requires having the http:/localhost/VirtualService installed on IIS7 prior to pressing the button XYZ. Once the button has been pressed, the first request to IIS will load the VirtualService (in this example, Worfklow6). Note, IIS will manage all VirtualServices individually, based on declaration in the .svc file.

Step 8 - Hosting in the WindowsService

This step requires closing the Host console program (we can not have duplicate ports) and installation of the VirtualServiceHost service. Once the service is started, the Bootstrap will ask a LocalRepository hosted in the Tester program for metadata. Once we have a DomainLoader service booted, we can load other VirtualServices based on the business needs.

That's all for the built-in testing features. You can review the Test folder implementation and add more custom testing; for example, changing metadata in the Tester or Host, endpoints, transports, etc.

Final note: The VirtualService implementation described in this article is not a production version. The implementation is missing a few things such as error handling, logging messages, pluggable components for security and policy handling, p2p channel extensions, etc. The article focuses on the virtualization of the services driven by the metadata model and the position in the business integration process.

Conclusion

This article attempts to show the usage of the workflow services in an integration process based on metadata. The XOML activated workflow enables this integration process to be created in the design phase as a part of the logical metadata driven model. As you could see in this article, the concept of the VirtualService is heavily based on metadata. It is very hard to troubleshoot the business connectivity and integration without the right tools. Coming to my point, building more tools is a great investment for every product, especially when the product is driven by metadata. Of course, there is an additional development cost, but by using some common descriptions of the metadata definition such as XAML, XOML, WCF service model, etc., we can save the company's budgets.

References:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)