WCF: 4 Tenets of Service Oriented Data Validation

Remember the 4 tenets of SOA?  One of them is that Boundaries are explicit.  When somebody sends data to your service it is just like when you cross an international border into another country.  Just a couple of hours drive north of us in Redmond is the border crossing to Canada.  When you cross into Canada or back into the United States you have to stop your car and the border agents do their job.  Their job is to make sure that you have proper documentation and that you aren’t smuggling something (or someone) bad into the country.

Your service has to have a similar border checkpoint and it is at the trust boundary where data enters your “country”.  At the boundary you have to validate the data before it gets down deep into your business logic or database in some invalid form.  The question I want to focus on here is one of design.  Where should the validation be done?

Download Sample Code – WCF Service Fault and Validation Example

For Workflow Services see endpoint.tv - WF4 Workflow Service Data Validation Design

Service Operation

Most of the time we tend to validate data on entry to the service method.  In small applications this approach is manageable but suppose you have a number (call it a Foo) that you use in 15 different services and you always pass it as an integer.  As you review the system you find that some services reject any negative Foo value while others reject any Foo value less than 1.  Your refactoring instinct tells you that it would be a good idea to centralize the Foo validation logic so you don’t end up with a variety of different validation rules.

Take a look at this code.  It works, but could it be better?

 public bool SomeOperation(int foo)
 {
     if (foo < 0)
     {
         throw new FaultException("Invalid Foo");
     }
  
     return ProcessTheFoo(foo);
 }

The problem with this code is that

  1. The validation rule (foo must be greater than zero) is contained within this service method.  If you use foo in another method or another service where it has the same semantic you have to be sure that your validation rule is followed there are well.  Once you code the rule in more than one place you have a system that is difficult to maintain and prone to inconsistent validation.

  2. The response to failed validation is also contained within the service method.  In this case you throw a FaultException but there are many options for how to Fault the service (as I mentioned in my previous post) and once again the way in which you respond to the failure of validation now becomes spread across your codebase resulting in a fragile system.

Ron’s 4 Tenets of Service Oriented Data Validation

Services have to consume and receive data.  This data flows across the service boundary and therefore must be untrusted until validated.  I’m proposing some new tenets for service orientation.  These tenets describe validation rules.  Validation rules are an expression that tells you if the data is valid or not.

  1. Validation Rules should be in one place
  2. Validation Rules should apply to all layers of the app
  3. Validation Rule violations should result in internal exceptions which may cause external faults
  4. Validation Rules may be shared between sender and receiver

Validation Rules should be in one place

In your system you have to validate the state of an object.  The validation rules for object should be written once and only once.  This makes your system more maintainable..

Validation Rules should apply to all layers of the app

Service Oriented Applications consist of the service boundary and lower layers or business logic.  What makes an object valid at one layer should be the same as what makes it valid at another layer.  Lower layers of the system may use internal types which hold data in intermediate states that is not valid according to the rules.  When this is the case you should think of these types as being fundamentally different than the type that the validation rules apply to.

Validation Rule violations should result in internal exceptions which may cause external faults

If validation is called from internal service logic, validation rules should throw exceptions types appropriate for internal use such as ArgumentNullException or ArgumentOutOfRangeException.  At the service boundary if you want to propagate the error to the sender these exceptions must be converted to FaultException or FaultException<TDetail>

Validation Rules may be shared between sender and receiver

If the sender and receiver are able and willing to accept the tighter coupling that comes from sharing assemblies, you can share validation rules between sender and receiver.  If you share validation rules, the sharing should be limited to only the types exposed at the boundary

Scenario: Self-Validating Request Message

Given

  • an entity named Foo which implements the IFoo interface
 public interface IFoo
{
    int Data { get; set; }
    string Text { get; set; }
}
  • a DataContract type named GetDataRequest with a property named Foo of type IFoo
  • A class Foo that implements IFoo and does data validation in the property setter
  • A class FooValidator that validates data on behalf of Foo
  • An InternalFoo class that implements internal business logic

When

  • The service is invoked with invalid data

Then

  • WCF deserializes the message body and creates an instance of Foo
  • The Foo property setters run validation code using the FooValidator
  • the FooValidator methods throw ArgumentException
  • and the Foo class converts the ArgumentException to a FaultException<TDetail> and throws
  • The sender catches a FaultException<TDetail>

Conclusion

Sound complex?  Sure… but it is one thing to build a simple example and quite another to show an architecture style that yields some significant benefits.  Of course there are many ways to accomplish these goals – you might have a better way – if so, please share it with me.

Happy Coding!

Ron

https://blogs.msdn.com/rjacobs

Twitter: @ronljacobs