Objects, State, and Data: The Buxom Server
In an object, the data can ultimately come from the two ends of a tiered system: the user (or some other form of) input, or some persistent data storage (usually a database). Some data is transient, ephemeral, short-lived stuff-the data equivalent of a mayfly. Most, however, requires storage and has persistence. It is typically a design goal of scalable COM-based systems that the objects they are made of are as stateless as possible. In other words, specific instances of a class (and thus specific sets of data) stay "live" and in memory for the briefest time. There are many good reasons for this. Principally this is so that we minimize the impact of idle instantiated objects on server system resources such as CPU and memory. At TMS, for any sizable system we typically would attempt to assess the likely impact of this on a server, as early as possible in the design process (see the scalability example spreadsheet in the samples directory for this chapter).
There is always a compromise to be made between the benefits of preinstantiation and the overhead of idle instances. Consider the case of the Buxom server-a publicly instantiable class provided by an ActiveX EXE server, sitting on a machine (The Bar) remote from its clients (thirsty punters like you and I). Its purpose is to stand as the software equivalent of a bartender in a system. Consider the process of ordering a round of drinks from an instance of the Buxom Server.
Preinstantiation: Your Object Hiring Policy
When a customer walks up to the bar with tongue hanging out, the management can have taken one of two diametrically opposed attitudes to hiring.
Hire, Serve, and Fire Management can see the customer and go out and hire a new Buxom server, because there is someone to serve. This will entail advertising, or perhaps going through a staffing agency; interviewing, hiring, perhaps even training. Eventually, there will be a Buxom server to serve the customer. Imagine the customer is typically English (used to queuing and waiting and therefore uncomplaining), so he is still there when the Buxom server arrives in her new uniform to take the order. The Buxom server serves the customer, and as soon as the cash is in the register, management fires her.
What I've just described is the equivalent of setting the class Instancing property of the Buxom server to SingleUse. This means that each time a customer wants to get a round of drinks from a Buxom server, we let COM handle the request by starting up a new instance of the ActiveX EXE component, which passes the customer an object reference to a new Buxom server instance. The customer uses the server's properties and methods to get a round of drinks, and then sets the object reference to Nothing. The reference to the Buxom server instance drops to zero, COM unloads the class instance and the component that provides it from memory. They are only in memory when a customer uses them. This is good for server resource management, but tough on the customer's patience. This was a very Visual Basic 4 implementation, since the alternative of MultiUse instancing without multithreading led to blocked requests for service.
Preinstantiation: a staff pool Management has estimated (the trouble of course with estimates is that they have a habit of transforming into promises) that a pool of four permanent bartenders will be enough to handle the volume of customers. They may have to be a little flexible and augment the staff with temporary help at peak periods (happy hour?). So they have already hired. Now when a customer comes up to the bar-as long as it isn't a terribly busy time-an instance of a Buxom server says, "Hi, what can I get you?" This time the customer doesn't have to wait for the hiring process to be completed before an order for drinks can be taken.
This is the equivalent of creating a pool manager (did you ever look at that sample?), which holds object references to instances of the Buxom server class and keeps them instantiated when they aren't busy. You can build a separate, generic pool manager, or a multithreaded ActiveX Server that only provides Buxom servers. Typically pool managers start as standalone ActiveX programs, which have public methods for getting a server and a form to keep the pool-managing component itself alive. Or, they use Sub Main in a novel way to provide a message loop, rather than just processing sequentially. Now the Buxom server must have teardown code to clean its properties (its state) after usage, so that when it is kept alive, it can be used by each customer as if it were newly instantiated. This will avoid the large component instantiation overhead, but will place some overhead of idle instances on server resources. However, with good algorithms to manage the pool size dynamically, the overhead can be kept to a minimum, and is at least under control.
This is the stateless basis of MTS systems. MTS, however, doesn't exactly do preinstantiation, but rather keeps instances that have been used but are now idle alive and waiting for customers for a period of time before allowing them to close and drop out of memory. This is how most Visual Basic systems scale without infinite hardware-by minimizing the number of "live" objects in the system at any given time and thus the amount of resources being used. This is also how- from the database developer's point of view-we can minimize numbers of database locks and connections: by combining object pooling and database connection pooling.
State and the Buxom server
Let's look a little closer at the task of ordering a round of drinks-something I feel well qualified to discuss. We go up to the bar and catch the attention of a Buxom server (we have an object reference to her). This is an art form in itself in some bars. We begin the process of ordering, in answer to her polite, "Hi, what can I get you?" We use her OrderDrink method to ask for a pint of Scruttocks Old Dirigible (American translation: a microbrew). She pulls our pint. We wait, since we are synchronously bound. We order a gin and tonic. "Do you want ice and lemon in that?" We don't know-Marmaduke has only just joined the development team. We can probably guess he does, but courtesy and politics dictate that we ask (he's project manager). We tell the Buxom server that we'll check and get back to her. Only thing is, he is sitting in the back room with the others, and the bar is very crowded. We fight our way through, check on his ice and lemon preference (he wants both), and fight our way back. All this time our Buxom server has been idle, consuming system resources but unable to service another order without abandoning her state data.
We carry on with our order: more drinks and some snacks (pork rinds). Then we hand over the money, get our change, and even ask for a tray to carry it all on. We have been interacting with a stateful, conversational server, passing data back and forth. We have been calling methods, or even directly interacting with our Buxom server's properties (I said we'd try and spice up data access at the outset). Since the Buxom server was instantiated on one side of the bar, and we are on the other, we have also been incurring network overhead with each exchange. For long periods one or other of us has also been idle, due to the latency of the network and the crowds in the bar. What's the alternative?
Stateless drinking: maximizing your drinking time
So instead of the unnecessary idling, we implement a single method in the Buxom server, called OrderRound. The OrderRound method carries all the data with it: an indication of the drinks and snacks we want, the Booleans to indicate we want a tray and that there will be no tip (stateless and mean), even the cash. The returns from OrderRound are our round and also some change, we hope. What advantages does this have?
To start, one call and one set of network overhead. Minimal idle time-in fact, we could even make it asynchronous by telling the Buxom server where we're sitting, and she could deliver the drinks (or one of those awful public address systems, firing an event to say our burgers are cooked-there's notable service in most of the places I frequent-far too upscale). As soon as the Buxom server is free, she can dump the state of our order -all taken in as parameters on one method anyway, so no global data required-and free herself up for the next customer. Such a system will scale better, since a single Buxom server ought to be able to turn around more orders in a given period of time.
This is the ultimate statelessness. This is what MTS systems crave (using SetComplete to indicate order completion), although it can be an awkward way of thinking for some developers. It is also what most database access techniques would ideally like to achieve. In truth, most of the time we will be somewhere in between highly stateful and stateless, but at least we know what to push toward in our design.
So the responsibilities of a data access strategy or design don't begin and end at getting data out and into the database; they have to include the dataflow (now there's an IT system term you don't hear so much anymore) around the system. If that system is composed of a number of components spread across a network, an intranet, or even the Internet, this dataflow aspect can take on a great deal of significance, since it is likely to be where performance is won or lost.