Section 4.2. Per-Call Services


4.2. Per-Call Services

When the service type is configured for per-call activation, a service instance (the CLR object) exists only while a client call is in progress. Every client request (that is, a method call on the WCF contract) gets a new dedicated service instance. The following list details how per call activation works; its steps are illustrated in Figure 4-1.

  1. The client calls the proxy and the proxy forwards the call to the service.

  2. WCF creates a service instance and calls the method on it.

  3. When the method call returns, if the object implements IDisposable, WCF calls IDisposable.Dispose( ) on it.

  4. The client calls the proxy and the proxy forwards the call to the service.

  5. WCF creates an object and calls the method on it.

Figure 4-1. Per-call instantiation mode


Disposing of the service instance is an interesting point. As I just mentioned, if the service supports the IDisposable interface, WCF will automatically call the Dispose( ) method, allowing the service to perform any required cleanup. Note that Dispose( ) is called on the same thread that dispatched the original method call, and that Dispose( ) has an operation context (presented later on). Once Dispose( ) is called, WCF disconnects the instance from the rest of the WCF infrastructure, making it a candidate for garbage collection.

4.2.1. Benefits of Per-Call Services

In the classic client-server programming model, using languages such as C++ or C#. every client gets its own dedicated server object. The fundamental problem with this approach is that it doesn't scale well. The server object may hold expensive or scarce resources such as database connections, communication ports, or files. Imagine an application that has to serve many clients. Typically, these clients create the objects they need when the client application starts and dispose of them when the client application shuts down. What impedes scalability with the client-server model is that the client applications can hold onto objects for long periods of time, while actually using the objects for only a fraction of that time. If you allocate an object for each client, you will tie up such crucial or limited resources for long periods and will eventually run out of resources.

A better activation model is to allocate an object for a client only while a call is in progress from the client to the service. That way, you have to create and maintain in memory only as many objects as there are concurrent calls, not as many objects as there are outstanding clients. In a typical Enterprise system only 1 percent of all clients make concurrent calls (a high-load Enterprise system has 3 percent of concurrent calls). If your system can concurrently sustain only 100 expensive service instances, this means it can typically serve as many as 10,000 outstanding clients. This is exactly what the per-call instance activation mode offers, because in between calls the client holds a reference on a proxy that doesn't have an actual object at the end of the wire. The obvious benefit of that is the fact that you can now dispose of the expensive resources the service instance occupies long before the client disposes of the proxy. By that same token, acquiring the resources is postponed until they are actually needed by a client.

Keep in mind that creating and destroying a service instance repeatedly on the service side without tearing down the connection to the client (with its client-side proxy) is a lot cheaper than creating an instance and a connection. The second benefit is that forcing the service instance to reallocate or connect to its resources on every call caters very well to transactional resources and transactional programming as discussed in Chapter 7, because it eases the task of enforcing consistency with the instance state. The third benefit of per-call services is that they can be used in conjunction with queued disconnected calls as described in Chapter 9, because they allow easy mapping of service instances to discrete queued messages.

4.2.2. Configuring Per-Call Services

To configure a service type as a per-call service, you apply the ServiceBehavior attribute with the InstanceContextMode property set to InstanceContextMode.PerCall:

 [ServiceContract] interface IMyContract {...} [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyService : IMyContract {...} 

Example 4-2 lists a simple per-call service and its client. As you can see from the program output, for each client method call a new service instance is constructed.

Example 4-2. Per-call service and client

 ///////////////////////// Service code ///////////////////// [ServiceContract] interface IMyContract {    [OperationContract]    void MyMethod( ); } [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyService : IMyContract,IDisposable {    int m_Counter = 0;    MyService( )    {       Trace.WriteLine("MyService.MyService( )");    }    public void MyMethod( )    {       m_Counter++;       Trace.WriteLine("Counter = " + m_Counter);    }    public void Dispose( )    {       Trace.WriteLine("MyService.Dispose( )");    } } ///////////////////////// Client code ///////////////////// MyContractClient proxy = new MyContractClient( ); proxy.MyMethod( ); proxy.MyMethod( ); proxy.Close( ); //Possible Output MyService.MyService( ) Counter = 1 MyService.Dispose( ) MyService.MyService( ) Counter = 1 MyService.Dispose( ) 

4.2.3. Designing Per-Call Services

Although in theory you can use per-call instance activation mode on any service type, in practice you need to design the service and its contracts to support the per-call activation mode from the ground up. The main problem is that the client doesn't know it's getting a new instance each time. Per-call services must be state-aware; that is, they must proactively manage their state, giving the client the illusion of a continuous session. A state-aware service isn't the same as a stateless service. In fact, if the per-call service were truly stateless, there would be no need for per-call activation in the first place. It is precisely because it has state, and an expensive state at that, that you need the per-call mode. An instance of a per-call service is created just before every method call and is destroyed immediately after each call. Therefore, at the beginning of each call, the object should initialize its state from values saved in some storage, and at the end of the call it should return its state to the storage. Such storage is typically either a database or the filesystem, but it can be a volatile storage like static variables.

Not all of the object's state can be saved as is, however. For example, if the state contains a database connection, the object must reacquire the connection at construction or at the beginning of every call (or at the constructor) and dispose of the connection at the end of the call or in its implementation of IDisposable.Dispose( ). Using per-call instance mode has one important implication for operation design: every operation must include a parameter to identify the service instance whose state needs to be retrieved. The instance uses that parameter to get its state from the storage and not the state of another instance of the same type. Examples for such parameters are the account number for bank account service, the order number for services processing orders, and so on. Example 4-3 shows a template for implementing a per-call service.

Example 4-3. Implementing a per-call service

 [DataContract] class Param {...} [ServiceContract] interface IMyContract {    [OperationContract]    void MyMethod(Param stateIdentifier); } [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyPerCallService : IMyContract,IDisposable {    public void MyMethod(Param stateIdentifier)    {       GetState(stateIdentifier);       DoWork( );       SaveState(stateIdentifier);    }    void GetState(Param stateIdentifier)    {...}    void DoWork( )    {...}    void SaveState(Param stateIdentifier)    {...}    public void Dispose( )    {...} } 

The class implements the MyMethod( ) operation, which accepts a parameter of type Param (a pseudotype invented for this example) that identifies the instance:

 public void MyMethod(Param stateIdentifier); 

The instance then uses the identifier to retrieve its state and to save the state back at the end of the method call. Any piece of state that is common to all clients can be allocated at the constructor and disposed of in Dispose( ).

Also, the per-call activation mode works best when the amount of work to be done in each method call is finite, and there are no more activities to complete in the background once a method returns. For this reason, you should not spin off background threads or dispatch asynchronous calls back into the instance, because the object will be discarded once the method returns. Because the per-call service retrieves its state from some storage in every method call, per-call services work very well in conjunction with a load-balancing machine, as long as the state repository is some global resource accessible to all machines. The load balancer can redirect calls to different machines at will, knowing that each per-call service can execute the call after retrieving its state.

4.2.3.1. Per-call and performance

Per-call services clearly offer a trade-off in performance (the overhead of reconstructing the instance state on each method call) with scalability (holding on to the state and the resources it ties in). There are no hard-and-fast rules as to when and to what extent you should trade some performance for a lot of scalability. You may need to profile your system and ultimately design some services to use per-call activation and some not to use it.

4.2.3.2. Cleanup operations

Whether or not the service type supports IDisposable is of no relevance to the client and is an implementation detail. In fact, the client has of course no way of calling the Dispose( ) method anyway. When you design a contract for a per-call service, avoid defining operations that are dedicated for state or resource cleanup, like this:

 //Avoid [ServiceContract] interface IMyContract {    void DoSomething( );    void Cleanup( ); } [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyPerCallService : IMyContract,IDisposable {    public void DoSomething( )    {...}    public void Cleanup( )    {...}    public void Dispose( )    {       Cleanup( );    } } 

The folly of such as design is obvious: if the client does call the cleanup method, it has the detrimental effect of creating an object just so the client can call Cleanup( ) on it, followed by a call to IDisposable.Dispose( ) (if present) by WCF for doing the cleanup again.

4.2.4. Choosing Per-Call Services

While the programming model of per-call services looks somewhat alien to client/server developers, per-call services are actually the preferred instance management mode for WCF services. The first argument in favor of per-call services is that they simply scale better. When designing a service, my golden rule for scalability is 10X. That is, every service should be designed to handle a load at least an order of magnitude greater that what its requirements call for. The reason for this is that in every other engineering discipline, engineers never design a system to handle its exact nominal specified load. You would not want to enter a building whose beams support exactly the load they were required to handle, ride in an elevator whose cable can handle exactly six passengers as stated on the elevator, and so on. Software systems are no differentwhy design a system for the specific current load while every other person in the company is working to increase business and the implied load? You should design software system to last years and sustain current and future loads. As a result, when using the 10X golden rule, you very quickly end up needing the scalability of the per-call service. The second argument in favor of per-call services is transactions. As you will see in Chapter 7, transactions are absolutely essential in any system, and per-call services lend themselves very well for the transactional programming model, regardless of the system load.




Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net