SOA Patterns with BizTalk Server 2013 and Microsoft Azure(Second Edition)
上QQ阅读APP看书,第一时间看更新

Implementing contracts in services

Once we have decided upon an interface definition for a service, we are able to move forward with the service which implements this interface. For those of you who have previously built .NET interface classes and then realized those interfaces in subsequent concrete classes, the WCF model is quite natural. In fact, it's the same. We build a concrete service class and choose to implement the WCF service contract defined earlier. For this example, we will take the previously built interface (which has since had its Insert operations replaced by a single operation that takes a data contract parameter) and implement the service logic.

Note

Consider creating distinct Visual Studio projects to house the service contract and the service implementation. This allows you to share the contract project with service consumers without sharing details of the service that realizes the contract. This idea is shown in the following screenshot:

An example code for the service contract is as follows:

public class VendorService : IVendorContract
{
    public void InsertVendor(VendorType newVendor)
    {
        System.Console.WriteLine("Vendor {0} inserted by service ...", newVendor.VendorId);

    }

    public bool DeleteVendor(string vendorId)
    {
        System.Console.WriteLine("Vendor {0} deleted ...", vendorId);

        return true;
    }
}

A WCF service may have metadata attributes applied to it in order to influence or dictate behavior. For instance, WCF has very robust support for creating and consuming transactional services. While specific attributes are applied directly to the service contract to affect how the service respects transactions, the attributes on the concrete service itself establish the way the service processes those transactions. In order to identify whether a service will accept transactions or not, an attribute is added to the service contract.

[ServiceContract(Name="VendorService", Namespace="http://BizTalkSOA/Contracts")]
    public interface IVendorContract
    {
        [OperationContract(Name="DeleteVendor")]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        bool DeleteVendor(string vendorId);
    }

However, this doesn't dictate the implementation details. That is left to the attributes on the service itself. The ServiceBehavior attribute has numerous available properties used to shape the activities of the service. Likewise, OperationBehavior applied to the implemented contract operations enables us to further refine the actions of the operation. In the following code snippet, I've instructed the service to put a tight rein on the transaction locks via the Serializable isolation level. Next, I commanded the DeleteVendor operation to either enlist in the flowed transaction or create a new one (TransactionScopeRequired) and to automatically commit the transaction upon operation conclusion (TransactionAutoComplete), as shown:

[ServiceBehavior(TransactionIsolationLevel= System.Transactions.IsolationLevel.Serializable)]
public class VendorService : IVendorContract
{
    [OperationBehavior(TransactionAutoComplete=true, TransactionScopeRequired=true)]
    public bool DeleteVendor(string vendorId)
    {
        System.Console.WriteLine("Vendor {0} deleted ...", vendorId);

        return true;
    }
}

Be aware of the nuances of where WCF attributes may be applied (for example, service contract, concrete service, and service operation) and the rich capabilities that these metadata tags can offer you.

Note

While this example showed how to attach transactions to services, you need to be extremely cautious and judicious with the usage of transactions across service boundaries. While WCF makes this seem transparent, try to make your services as encapsulated as possible so that they have few explicit or implied dependencies on other services

Throwing custom service faults

Whenever possible, you should avoid returning the full exception stack back to the service caller. You may inadvertently reveal security or implementation details that allow a malicious user to engage in mischief. The preference is to throw friendly/business and typed faults for the clients to handle.

Tip

Alternatively, in the config file in the service behavior section, we can set includeExceptionDetailInFaults="false" to avoid sending exception details to clients.

Within WCF, a fault contract is a custom data contract that allows you to shape the exception being returned to the service consumer. Let's say we defined a controlled fault contract that looks like this:

[DataContract(Name = "InsertFault")]
public class InsertFaultType
{
  private string friendlyMessage;
 
  [DataMember()]
  public string FriendlyMessage
  {
    get { return friendlyMessage; }
    set { friendlyMessage = value; }
  }
}

So far, this looks like any old data contract. And in reality, that's all it is. However, we can associate this fault contract with a particular operation by adding the FaultContract attribute to the operation in the service contract:

[OperationContract(Name="InsertVendor")]
[FaultContract(typeof(InsertFaultType))]
void InsertVendor(VendorType newVendor);

While implementing the service, we explicitly produce and throw these custom exception types back to the service consumer. This is done by catching the .NET exception and creating a new fault object from the custom fault we created earlier. Then, we throw a new FaultException typed to our custom fault definition:

public void InsertVendor(VendorType newVendor)
{
    try
    {
        //do complex database update ...
    }
    catch (System.Data.SqlClient.SqlException sqlEx)
    {
      //log actual fault to admin log

      //throwing SQL exception back to caller is bad.
      //Create new exception out of custom fault contract
      InsertFaultType insertFault = new InsertFaultType();
      insertFault.FriendlyMessage = "Insert operation failed";

      //throw custom fault
      throw new FaultException<InsertFaultType>(
         insertFault, 
         "illegal insert");

   }
}

By defining and throwing custom service faults, you achieve better control over how your service communicates to the outside world and can better insulate yourself from critical implementation leakage.