An SOA odyssey

Thursday, May 19, 2005

Service Agent

The third of the seven categories in our Services Platform is that of Service Agent. This pattern encapsulates the concerns of service consumers with regard to service discovery, security credentials, intelligent caching of time-sensitive data (e.g., token for a reservation message pattern), as well as invocations of the service of course.

The core tenets of how we use Service Agents in our platform is encapsulated in the statements below.

  • Service agents are required. Every .NET service consumer Compassion develops must employ a service agent. Therefore service developers must create a Service Agent for clients to use.
  • Service agents are client-side assembly. The Service Agent is deployed in a client-side .NET assembly other than the consuming service’s assembly
  • Service agents use a common interface. A service agent interface or base class is inherited by specific service agents. This enforces common behavior among service agents and allows for code reuse.
  • Service agents use helper classes. Service Agent Helper classes are general purpose utility classes that are invoked by the service agent and provides the implementation of specific functionality. For example, the communication with the service directory to implement service discovery is encapsulated in a helper class used by our service agent base class described below.

Essentially, when an application needs to use functionality provided in an external service, our service agent manages the semantics of communicating with that particular service. For example, the business components of a retail application could use a service agent to manage communication with the credit card authorization service, and use a second service agent to handle conversations with the courier service. Service agents isolate the idiosyncrasies of calling diverse services from applications, and can provide additional services, such as basic mapping between the format of the data exposed by the service and the format your application requires.

To assist with this standard a talented consultant, John McPherson of Interlink, created an elegant framework for building our service agents. For example, a ServiceAgentBase class inherits the following interface.

public interface IServiceAgent
{
XmlDocument ExecuteOperation(
string operation,
XmlDocument payload,
string messageId,
string messageStreamId,
string processId);

void ExecuteOperationNoResponse(
string operation,
XmlDocument payload,
string messageId,
string messageStreamId,
string processId);


string ApplicationName
{
get;
set;
}

string Type
{
get;
set;
}

string ReplyToAddress
{
get;
set;
}
}
As you can see our basic interface allows for request-response and one-way message exchange patterns through the ExecuteOperation and ExecuteOperationNoResponse methods. The service agent also allows for specifying the application name, communication type to use, and the address to reply to. You'll notice that these method signatures also include three ids - message, message stream, and process. These identifiers are placed into the header of the SOAP message so that our service management features are able to track how messages flow through our SOA. We've defined a standard SOAP header that I'll lay out in a future post.

The ServiceAgentBase class then exposes these methods as protected so that derived classes may use them when implementing specific methods that map to service operations. This makes the derived classes very light allowing us to implement methods that map to service operations using less than 25 lines of code. These derived service agent classes use specific schemas for request and response messages built using the XSD.exe tool that ships with the .NET Framework SDK. The schemas are originally built in BizTalk since our Process Services use them. We experimented with using the XSDObjGen tool to generate the "bind" classes but found it had several limitations when dealing with schemas with "any" elements and so we went back to XSD although it doesn't do as good a job of representing repeating elements since it doesn't use collection classes. In a future post I'll talk about our schema standards and how we plan to support extensibility.

We chose not to use the same technique (namely inheriting frmo SoapHttpClientProtocol) Visual Studio does when creating these service agents or proxies for a couple of reasons. First and foremost, our service agents allow for invoking services over several transports which include SOAP over HTTP and SOAP over MSMQ. The SoapHttpClientProtocol class obviously does not include support for MSMQ. Secondly, our service agents create and consume specific SOAP headers as mentioned previously.

The ServiceAgentBase class relies on a set of helper classes to interact with the service directory such as UDDIServiceDirectory that inherits the IServiceDirectory interface I discussed in a previous post. The ServiceAgentBase can then read configuration information using a configuration section handler class and either load and parse the service contract (WSDL 1.1) from a local path or query the service directory based on a service key (GUID) to download and parse the WSDL. The service agent relies on the WSDL to set the SOAP action and the endpoint at which to communicate with the service.

The framework also supports the idea of invoking services through a generic proxy class called ServiceAgentProxy. This class accepts payload XmlDocument objects and uses its configuration information to create and send the SOAP messages without the client having to explicitly reference assemblies that contain specific service agent classes. This functionality has been useful in two respects already. First, we use it when invoking services from inside BizTalk orchestrations. Within BizTalk we've created a generic orchestration that subscribes to messages that contain all the metadata about the call to make as well as the payload. It then uses the ServiceAgentProxy to make the call. Secondly, we're planning on using this approach to provide a way to invoke our services from ASP 3.0 code (which is a requirement in our first deliverable). Since we didn't want to use COM interop on the ASP server we created a Service Agent Broker ASP.NET site that accepts HTTP posts that contain the metadata about the call to make as well as the payload. The broker then uses ServiceAgentProxy to make the call and return the results to the ASP site using a Response.Write.

While we haven't added more sophisticated features we are planning on using service agents to do client side caching of reference data as well as using it to implement more complex message exchange patterns such as asynchronous request response using the reservation pattern.