One of the cornerstones of creating multi-tier applications that can easily be scaled up and scaled out is the avoidance of per-client state in the server-side objects.
Service objects that maintain per-client state between calls must remain live for the entire duration the client is working with the server, so that the state contained within the object remains available for every call made by the client. What's more, because state is specific to individual clients, separate copies of the server objects must be kept alive for each individual client.
As your server scales up and the number of clients talking to the server increases, the number of active service instances will increase, and with it the memory footprint of your server application.
Assume you have a server that is capable of handling 100 requests per second, and your average client application will talk to the server every 5 minutes. This would allow your application to serve roughly 30,000 individual clients. Furthermore, assume that every service instance will consume a moderate 10 kB of memory - a conservative assumption for a business object and reasonable working data.
The total memory consumption for all active business objects alone would be roughly 300 MB. At first sight, this might seem like an acceptable load for a dedicated application server - but what happens when you need to scale up? Say you upgrade your connection and CPU to allow 1000 requests per second. Also you might redesign your client application to only talk to the server every 10 minutes, on average, to increase the number of clients you can serve. But as your clients increase to 600.000 and beyond, the memory load keep increasing with them, to 6000 GB.
Clearly, this approach does not scale well.
Enter Stateless Servers
If you consider the numbers above, it is apparent that between calls each per-client service instance is sitting idle for a considerable amount of time (4:59 of 5 minutes), waiting for the next call. Since your server is handling 100 requests per second, only 1 MB of memory is actually in active use - the remainder is just waiting for other clients that may - or may not - call sometime in the future.
Service objects designed for statelessness will allow your server to scale up much better, as you will not need to keep individual instances of the service around for each client. Instead, your server will only keep the minimum set of 100 service instances active, and reuse these to process all the requests. After an object is done processing one request, rather then being put to sleep for an average of 5 minutes, it will go right on to service the next request (from a different client).
How to Design for Statelessness
The key factor for writing stateless server objects is to write your code so that it does not rely on client-specific information to be stored in fields of your class and be reused between different calls. An example of stateful design would be loading a large set of data into the service during one call from a client, and working with this data on subsequent calls (say, repeated calls to fetch additional rows of a record).
In a stateless server object, each call from the client would be completely independent and not rely on data left over from previous calls. For example, a call to fetch incremental data could pass back the offset from the last call, so the server would know where to resume.
A good practice to make sure your server objects remain stateless is to simply not define any fields for the class, but access only method-local variables and parameters passed from the client.
Class factories for Statelessness
Remoting SDK provides three Class Factories that work well with stateless services:
- The Singleton Class Factory works well for perfectly stateless server objects, as it forces all calls to be handled by the very same service instance. If your service object is well designed to not use any class fields (and thus is fully thread-safe and reentrant), there is no better and memory-friendlier way then to have one single instance process all requests.
- The Pooled Class Factory provides the next best level of stateless services by providing a pool of objects that will be reused to execute requests. If your class does rely on class fields during the execution of individual requests or is for other reasons not fully reentrant, this is your best option, as you can be sure that each instance will only serve one request at a time. However you must sure to not leave client state behind when your calls exit, as the individual instances will be executing requests for other clients.
- The Per Request Factory not only supports but practically enforces statelessness, as it will destroy each service instance after a call finishes - losing any client state - and create a new one for the next call.
Maintaining State in a Stateless World
Sometimes it is sensible to maintain a little bit of client state between calls, even if your application is designed for statelessness. For example, your might want to implement access control and provide a Login method to initiate authentication, rather than retransmitting the password for each and every remote call. This can be achieved through so called Sessions that allow you to store per-client information outside of your service objects. It will be up to your service implementation to store this data inside the session before a call finishes, and obtain it again, when needed, in subsequent calls.
Remoting SDK provides extensive Session Management capabilities to make this easy.