Service Contracts Factoring and Design





Service Contracts Factoring and Design

Syntax aside, how do you go about designing service contracts? How do you know which operations to allocate to which service contract? How many operations should each contract have? Answering these questions has little to do with WCF and a lot to do with abstract service-oriented analysis and design. An in-depth discussion of how to decompose a system into services and how to discover contract methods is beyond the scope of this book. Nonetheless, this section offers a few pieces of advice to guide you in your service contracts design effort.

Contract Factoring

A service contract is a grouping of logically related operations. What constitutes "logically related" is usually domain-specific. You can think of service contracts as different facets of some entity. Once you have identified (after requirements analysis) all the operations the entity supports, you need to allocate them to contracts. This is called service contract factoring. When you factor a service contract, always think in terms of reusable elements. In a service-oriented application, the basic unit of reuse is the service contract. Would this particular contract factoring yield contracts that other entities in the system can reuse? What facets of the entity can logically be factored out and used by other entities?

As a concrete yet simple example, suppose you wish to model a dog service. The requirements are that the dog should be able to bark and fetch, that the dog should have a veterinary clinic registration number, and that you could vaccinate it. You can define the IDog service contract and have different kinds of services, such as the PoodleService and the GermanShepherdService implement the IDog contract:

[ServiceContract]
interface IDog
{
   [OperationContract]
   void Fetch( );

   [OperationContract]
   void Bark( );

   [OperationContract]
   long GetVetClinicNumber( );

   [OperationContract]
   void Vaccinate( );
}
class PoodleService : IDog
{...}
class GermanShepherdService : IDog
{...}

However, such a composition of the IDog service contract is not well factored. Even though all the operations are things a dog should support, Fetch( ) and Bark( ) are more logically related to each other than to GetVetClinicNumber( ) and Vaccinate( ). Fetch( ) and Bark( ) involve one facet of the dog, as a living, active canine entity, while GetVetClinicNumber( ) and Vaccinate( ) involve a different facet, one that relates it as a record of a pet in a veterinary clinic. A better approach is to factor out the GetVetClinicNumber( ) and Vaccinate( ) operations to a separate contract called IPet:

[ServiceContract]
interface IPet
{
   [OperationContract]
   long GetVetClinicNumber( );

   [OperationContract]
   void Vaccinate( );
}

[ServiceContract]
interface IDog
{
   [OperationContract]
   void Fetch( );

   [OperationContract]
   void Bark( );
}

Because the pet facet is independent of the canine facet, other entities (such as cats) can reuse the IPet service contract and support it:

[ServiceContract]
interface ICat
{
   [OperationContract]
   void Purr( );

   [OperationContract]
   void CatchMouse( );
}

class PoodleService : IDog,IPet
{...}

class SiameseService : ICat,IPet
{...}

This factoring, in turn, allows you to decouple the clinic-management aspect of the application from the actual service (be it dogs or cats). Factoring operations into separate interfaces is usually done when there is a weak logical relation between the operations. However, identical operations are sometimes found in several unrelated contracts, and these operations are logically related to their respective contracts. For example, both cats and dogs need to shed fur and feed their offspring. Logically, shedding is just a dog operation as is barking, and is also just a cat operation as is purring.

In such cases, you can factor the service contracts into a hierarchy of contracts instead of separate contracts:

[ServiceContract]
interface IMammal
{
   [OperationContract]
   void ShedFur( );

   [OperationContract]
   void Lactate( );
}
[ServiceContract]
interface IDog : IMammal
{...}

[ServiceContract]
interface ICat : IMammal
{...}

Factoring Metrics

As you can see, proper contract-factoring results in more specialized, loosely coupled, fine-tuned, and reusable contracts, and subsequently, those benefits apply to the system as well. In general, contract factoring results in contracts with fewer operations.

When you design a service-based system, however, you need to balance out two countering forces (see Figure). One is the cost of implementing the service contracts and the other is the cost of putting them together or integrating them into a cohesive application.

Balancing the number of services and their size


If you have too many granular service contracts, it will be easy to implement each contract, but the overall cost of integrating all those service contracts will be prohibitive. On the other hand, if you have only a few complex, large service contracts, the cost of implementing those contracts will be a prohibitive factor, even though the cost of integrating them might be low.

The relationship between cost and size of a service contract to implementation is not linear, because complexity is not linear to sizesomething twice as big is four or six times as complex. Similarly, the relationship between integration cost and the number of service contracts to integrate is not linear, because the number of possible connections is not linear to the number of participating services.

In any given system, the total effort involved in designing and maintaining the services that implement the contracts is the sum of those two factors (cost of implementation and the cost of integration). As you can see from Figure, there is an area of minimum cost or effort in relation to the size and number of the service contracts. A well-designed system has not too many but not too few services, and those services are not too big but not too small.

Because these contracts-factoring issues are independent of the service technology used, I can extrapolate from my own and others' experiences of factoring and architecting large-scale applications and share a few rules of thumb and metrics I have collected about service contracts factoring.

Service contracts with just one operation are possible, but you should avoid them. A service contract is a facet of an entity, and that facet must be pretty dull if you can express it with just one operation. Examine that single operation: is it using too many parameters? Is it too coarse, and therefore, should it be factored into several operations? Should you factor this operation into an already existing service contract?

The optimal number of service contracts members (in my opinion and experience) is between three and five. If you design a service contract with more operationssay, six to nineyou are still doing relatively well. However, try to look at the operations to determine whether any can be collapsed into each other, since it's quite possible to overfactor operations. If you have a service contract with 12 or more operations, you should definitely find ways to factor the operations into either separate service contracts or a hierarchy of contracts. Your coding standard should set some upper limit never to be exceeded, regardless of the circumstances (say, 20).

Another rule involves the use of property-like operations such as:

[OperationContract]
long GetVetClinicNumber( );

You should avoid such operations. Service contracts allow clients to invoke abstract operations, without caring about actual implementation details. Property-like operations are known as just-enough-encapsulation. You can encapsulate the business logic of setting and reading that variable's value on the service side, but ideally, you shouldn't bother clients with properties at all. Clients should invoke operations and let the service worry about how to manage its state. The interaction should be in terms of DoSomething( ) like Vaccinate( ). How the service goes about doing that and whether or not a vet clinic number was involved should be of no concern to the client.

A word of caution about factoring metrics: rules of thumb and generic metrics are only tools to help you gauge and evaluate your particular design. There is no substitute for domain expertise and experience. Always be practical, apply judgment, and question what you do in light of these guidelines.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows