Server-Activated Single Call
The fundamental problem with the client-activated object model is that it does not scale well. The server object may hold expensive or scarce resources such as database connections, communication ports, files, or it may allocate a large amount of memory, etc. Imagine an application that has to service many clients. Typically, these clients will create the remote objects they need when the client application starts, and dispose of them when the client application shuts down. What impedes scalability with client-activated objects is the potential that the client applications have for holding onto objects for long periods of time while actually using the object only in a fraction of that time. If your design calls for allocating an object for each client, you may tie up such crucial limited resources for long periods and your application may eventually run out of resources.
|When the client uses a server activated single-call object, for each method call .NET creates a new object, lets it service the call, and then disposes of it.|
A better object model design will allocate an object for a client only while a call is in progress from the client to the object. That way you will only have to create and maintain in memory as many objects as there are concurrent calls, not as many objects as there are clients. This is exactly what the single call activation model is about: when the client uses a server activated single-call object
, for each method call .NET creates a new object, lets it service the call, and then disposes of it. Between calls the client holds a reference on a proxy that does not have an actual object at the server end of the wire. Figure 3
shows how single call activation works:
|Figure 3: In the single-call activation model, .NET creates an object for each call, and disposes of it between calls.|
Benefits of single-call objects
- Object executes a method call on behalf of a remote client.
- When the method call returns, if the object implements IDisposable, .NET calls IDisposable.Dispose() on it. .NET then releases all references it has on the object, making it a candidate for garbage collection. Meanwhile, the client continues to hold a reference to a proxy and does not know that its object is gone.
- The client makes another call on the proxy.
- The proxy forwards the call to the remote domain.
- .NET creates an object and calls the method on it.
The obvious benefit of using a single-call object is that you can dispose of the expensive resources the object occupies long before the client disposes of the object. By that same token, acquiring the resources is postponed until they are actually needed by a client. Keep in mind that creating and destroying the object repeatedly on the object side, without tearing down the connection to the client (with its client-side proxy) is cheaper than creating and disposing of the object. Another benefit is that even if the client is not disciplined enough to explicitly dispose of the object, it has no effect on scalability because the object will be disposed of automatically. If the client does call IDisposable.Dispose()
on the object, it has a detrimental effect of recreating the object just so that the client can call Dispose()
on it. This will be followed by a second call to Dispose()
by the proxy. Make sure the object is designed to handle multiple calls on Dispose()
Designing a single-call object
Although in theory you can use single-call activation on any component type, in practice you need to design the component and its interfaces to support the single-call activation model from the ground up. The main problem is that the client does not know it is getting a new object each time. Single-call components must be state-aware
, that is, they must proactively manage their state, giving the client the illusion of a continuous session. A state aware object is not the same as a stateless object. In fact, if the single-call object were truly stateless, there would be no need for single-call activation in the first place. Because a single call object is created just before every method call and deactivated immediately after each call, then 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 should return its state to the storage. Such storage is typically either a database or the file system.
However, not all of the state of an object can be saved as-is. For example, if the state contains a database connection then the object must re-acquire the connection at the beginning of every call and dispose of the connection at the end of the call, or in its implementation of IDisposable.Dispose()
. Using the single-call activation model has one important implication for method design: every method call must include a parameter to identify the object whose state needs to be retrieved. The object 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 objects, the order number for objects processing orders, and so on. Listing 1
shows a template for implementing a single-call class.
The class provides the MyMethod()
method, which accepts a parameter of type Param
(a pseudo type invented for this example) used to identify the object:
public void MyMethod(Param objectIdentifier)
The object then uses the identifier to retrieve its state and to save the state back at the end of the method call.
Another design constraint of dealing with single-call objects is constructors. Because .NET re-creates the object automatically for each method call it does not know how to use parameterized constructors, or which parameters to provide to them. As a result, a single call object cannot have parameterized constructors. In addition, because the object is constructed only when a method call takes place, the actual construction call on the client side is never forwarded to the objects:
Applying the single-call model
//No constructor call is made:
obj = new MySingleCallComponent();
Single-call activation clearly offers a tradeoff in performance (the overhead of reconstructing the object's 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 performance for scalability. You may need to profile your system and ultimately redesign some objects to use single-call activation and some not to use it.
In addition, the single call activation model works only 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 object because the object will be disposed of once the method returns. In every method call the single-call object retrieves its state from some storage, so single-call objects 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 single-call object is capable of servicing the call after retrieving its state. (See Sidebar: Enterprise Services JITA