Overview

I've recently begun playing around with developing an SOA solution using WCF. My focus is on implementing a service that is loosely coupled, autonomous, highly configurable and reusable.  I might tell you more about it in a later blog, but for now I want to describe some of the design issues involved.

Architecting SOA solutions

This post is not a replacement for a good book on SOA, and I highly recommend Thomas Erl's various books on the subject.  You can find out more at http://www.soabooks.com.  I've also had the misfortune to read some bad books on the subject, so be careful when you research the subject!

I've called this post "designing web services" because I'm not going to address the architecture issues nor the implementation of the service itself.  Rather, I'm going to describe some of the high level implementation issues, such as message structure, contract-first development, data types and namespace issues.  Some of the key concerns of SOA that drive this process are listed below:

  • Service loose coupling
    • the service requestor should allow for multiple implementations of a service provider
    • the service provider should allow for multiple implementations of a service requestor
  • Service reuse
    • the service provider should be reusable in the widest sense realistically possible

I've focused on these concerns because they are supported by the concepts of service and message contracts defined in WSDL and XSD documents, which form a major part of service design.

Service contracts in WCF

A service contract in WCF represents the high level aspects of a WSDL document.  Represented by an interface, a service contract is a definition of a set of operations.  A service may be defined in WCF as follows:

[ServiceContract]
public interface IServiceContract
{
  [OperationContract]
  AnOpResponseMessage AnOperation(AnOpRequestMessage request);
}

Structured in this way, with a single request and response message type provides the greatest flexibility in messaging structure.  Additionally, it allows for the document-literal message style which provides the greatest possibility of integration, thereby supporting service reuse.

However, at this stage the service contract is missing several key components, and the message types have not been defined.  The latter will be covered in the next section, but the former can be dealt with below:

[ServiceContract(
  Name="IServiceContract",
  Namespace="urn:yourbusiness.com/services/YourService")]
public interface IServiceContract
{
  [OperationContract(Name="AnOperation")]
  AnOpResponseMessage AnOperation(AnOpRequestMessage request);
}

The ServiceContract and OperationContract attributes support additional, important parameters, however defining the namespace and name early ensures that the operations are well known, surviving changes in the method names themselves.  Relying on the WCF system for naming increases the possibility for errors lately.  These names and namespaces should be chosen carefully and then retained to ensure future compatibility with existing service clients.

Key point 1: the namespaces and names of ServiceContracts and OperationContracts are part of the contractual agreement with the client and are not an implementation decision but a business and architecture one.  They should not be changed once they have been defined, and should also survive changes to a brand name.

Message contracts

Any introductory book to WCF will mention "Data contracts", but it is important to look at message contracts first.  A data contract specifies the body of a message, but a message contract defines the entire request and response structure.  A message contract can require a custom message header, and it can specify that certain headers are digitally signed or encrypted.  Overlooking the message contract may prevent the easy and consistent support of messaging requirements.  A message contract is defined as a class.  Similarly to a service contract and an operation contract, attributes are used to define the various options:

[MessageContract(
  IsWrapped = true,
  ProtectionLevel = ProtectionLevel.None,
  WrapperName = "RequestMessage",
  WrapperNamespace = "urn:yourbusiness.com/services/YourService/AnOperation")]
public class AnOpRequestMessage
{
  [MessageBodyMember(
    Name="body",
    Namespace="urn:yourbusiness.com/services/YourService/AnOperation/request",
    ProtectionLevel = ProtectionLevel.None)]
  public AnOpRequestMessageBody body;
}

Using a wrapper ensures document-literal convention is used.  The name and namespace for both the wrapper and any items in the message needs to ensure that it doesn't conflict with any other service, operation or message, and so you need a naming scheme that supports this independence.  The one above ensures that "body" can be defined separately in both the request and response to AnOperation.

Note as well that the protection level is configured at both the wrapper and member level.  This determines the minimum acceptable protection, and therefore is a design decision.  The actual protection level used is determined by the administrator during the deployment of the service.  The protection level can differ on each header and between headers and the message body, but the message body must have only a single consistent protection level configured.

The actual data structure used by the message is defined by a separate type, AnOpRequestMessageBody.  This type may use the DataContractSerializer or the XmlSerializer, but in either case it is defined separately.  I'll describe the DataContract approach in the next section.

Key point 2: use message contracts and set the IsWrapped property to true.  Also, configure names and namespaces on message wrappers and elements to ensure there isn't a conflict with other operations.

Data contracts

Using a separate data contract class allows for extensibility without compromising the message or service contract.  Use a data contract type for custom message headers and for the message body (note: singular!).  A WCF data contract class is defined as below:

[DataContract(
  Name="body",
  Namespace="urn:yourbusiness.com/services/YourService/AnOperation/request/body")]
public class AnOpRequestMessageBody : IExtensibleDataObject
{
  [DataMember(Name = "exampleInteger",
    Order = 0)]
  public int exampleInteger;

  private ExtensionDataObject ExtensionData;
  ExtensionDataObject IExtensibleDataObject.ExtensionData
  {
    get { return this.ExtensionData; }
    set { this.ExtensionData = value; }
  }
}

Note again that the namespace and names of items is defined explicitly.  In this case, the namespace of the types public members is the same as the namespace of the data contract.  This differs from the message contract because the message contract is defining the namespace of the wrapper element and this is not inherited by the message elements.  However, the DataMembers do get the namespace of the DataContract automatically.

Two further points are worth noting.  Firstly, each public part of the type has an order specified.  This also helps the service to be reusable by ensuring that the contract is well defined, even if items are later grouped or rearranged.

Secondly, the type implements the IExtensibleDataObject interface so that the type can be extended later without compromising compatibility.  Of course, this feature must be designed into the classes that implement the actual processing as well.

Key point 3: use DataContracts and explicitly define the namespaces, names and order of members.  Additionally, implement the IExtensibleDataObject to allow the type to be extended later.

Implementing the service

To implement the service, create a class that implements the service contract (IServiceContract).  A single class can implement multiple service contracts if helpful.  The service can then be hosted using IIS or in an application.

Final remarks

It can be extremely easy to create services in WCF, perhaps too easy.  Good service design needs a contract-first approach and a real understanding of the business needs.  Using the following principles can help you to create reusable and loosely coupled services:

  1. Define namespaces and names explicitly.  This applies to service, operation, message and data contracts
  2. Implement document-literal contracts using a Service -> Operation -> MessageContract -> DataContract approach
  3. Define the order of data items in data contracts and allow for the data types to be extensible

Versions

  • Microsoft .NET Framework 3.5

Metadata


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist
posted @ Monday, April 28, 2008 11:19 AM | in SOA .NET Software Development

Comments

No comments posted yet.

Post Comment

Title *
Name *
Email
Url
Comment *  


Please add 3 and 8 and type the answer here: