Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WCF fault contracts in real world

Assume we have a service method that performs some security checks, retrieves data from DB and third-party web service, constructs MyDataDTO, writes an audit entry back into DB. And we want well structured, granular error codes, don't we? We're good boys and follow standard WCF error handling guidelines:

[FaultContract(typeof(AccessDenied))]
[FaultContract(typeof(KeyNotFound))]
[FaultContract(typeof(WsFault))]
[FaultContract(typeof(DbFault))]
MyDataDTO GetData(string key);

Now we're adding a new method that updates the data. The method calls GetData() internally (or major part of it), performs validation add updates the data. So it must have all faults of GetData() duplicated plus add its own faults:

[FaultContract(typeof(InvalidState))]
[FaultContract(typeof(DataNotValid))]
[FaultContract(typeof(AccessDenied))]
[FaultContract(typeof(KeyNotFound))]
[FaultContract(typeof(WsFault))]
[FaultContract(typeof(DbFault))]
void UpdateData(MyDataDTO data);

So far so good. This allows us even to xml generate documentation we can provide for consumers of our service so they know which error codes they can expect.

Now imagine we have 10 services with 10 methods like above (or even more complex) each. And defining all those fault contracts becomes nightmare as this is quite error-prone process:

  1. There is no way to define general faults (like DbFault) for the whole service
  2. You cannot guarantee that a fault defined on an operation contract will really be returned (copy-paste issues)
  3. You cannot guarantee that you did not miss some fault to add to the operation contract

Let's not take into account interface versioning here :)

So you got the picture if you support WCF services in production. Should we abandon fault contracts at all and use good old C-style (like having base DTOBase class with ErrorCode property)? Reduce error granularity? How to make sure the documentation is correct/up to date? I'm interested in some best practices.

like image 500
UserControl Avatar asked Jan 06 '12 13:01

UserControl


2 Answers

One problem with your original approach is that you are trying to replicate the multitude of system errors/exceptions that could occur. Since the complexity of the system increases with each new function, the number of possible problems you have to account for rises exponentially (or greater!)

I would suggest the following approach: Since you are creating a "system" of services and access calls, only define FaultContracts relating to that system. The clients should only be interested in the following questions:

  1. Is this a problem with the data I wanted, or the way in which I asked?
  2. If not, is this a problem with me, or is it an IT-related issue (system crash, network error, database issue, whatever)?

With a little rework, you can cut down on the number of fault contracts you have to provide. For example (and this is off the top of my head):

//Base class to define general problems
[DataContract]
public class SysFault
{
  //MyDataDTO-specific general error.
  [DataMember]
  public string SysMsg {get;set;}

  //boolean for "this is a problem with me or the hosting system?"
  [DataMember]
  public bool IsSystemic {get;set;}
}

//Subclass to expose synchronization issues--if that's one you want to define
[DataContract]
public class SyncFault : SysFault
{
  [DataMember]
  public string SyncMsg { get;set; }
}

//Subclass to expose validation issues
[DataContract]
public class ValFault : SysFault
{
  [DataMember]
  public string ValMsg { get;set; }
}

Now, you can use the fault types to separate out what is going on with your services. For example:

[ServiceContract]
public interface IRecordSys
{
    [OperationContract]
    [FaultContract(typeof(SysFault))]  //Raised for underlying problem
    [FaultContract(typeof(ValFault))]  //Raised if there is an issue with the key value
    MyDataDTO getData(string key);

    [OperationContract]
    [FaultContract(typeof(SysFault))]  //Raised for underlying problem elsewhere
    //Raised for some issue, such as unable to get two subsystems to update properly
    //with the given data
    [FaultContract(typeof(SyncFault))]
    void update(MyDataDTO data); 
}

Your particular implementation will differ, but the idea is to pass along messages that concern your system, not every little systemic problem that may come along.

like image 143
angus_thermopylae Avatar answered Sep 20 '22 22:09

angus_thermopylae


Well, you can implement this:

public void ProvideFault(Exception error, MessageVersion version, ref Message fault)

In that case you'll have one place, where you will switch by exception type/message and provide own faults.

http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler.providefault.aspx

or better yet with samples:

http://blogs.msdn.com/b/pedram/archive/2008/01/25/wcf-error-handling-and-some-best-practices.aspx

like image 24
Giedrius Avatar answered Sep 19 '22 22:09

Giedrius