Stateless and Event-Driven Programming

Contents Overview

A Progress WebSpeed or AppServer broker that is running in stateless operating mode does not dedicate an Application Server process or WebSpeed agent process to a client connection.  Instead, all such processes remain generally available to execute remote procedure requests from all connected client applications.

Because of this, "context" information (such as global variable values, temp-table contents, and persistent procedure invocations) is not preserved between one client request and the next. In fact, a client may end up "talking" to a different AppServer or WebSpeed process on each request.

Being able to run a distributed application in this stateless mode is very valuable, because a relatively small number of server processes can handle a larger number of users (as long as they don't all submit their requests at the exact same time). In general, a good rule of thumb is that one WebSpeed or AppServer process can handle roughly five (5) "active" users, or many more "occasional" users.

However, stateless mode also requires a major change in how applications are designed, compared to the procedural model used in old character-mode applications. Some of these important considerations are:


Event-Driven Flow of Execution

Because there is no constant connection between a user and a server process, there is no way for the server-side programs to control the flow of execution of the application. Instead, the flow is controlled by the end user on the client side, and the server-side programs exist only to "serve" requests made by the client process.

For developers who are coming from a character-mode, procedure-driven environment, this can sometimes cause temporary "paradigm shock" because it is conceptually so different from what they have done for years. Instead of the server code controlling the end user's options, now it becomes the servant of the end user application. It helps to mentally "turn" 180 degrees and look at everything from the client side, with the server code being "chunks" of logic that perform various tasks upon request and return the desired results to the client.

For a good review of the differences between event-driven and procedure-driven programming models, see Chapter 2 "Progamming Models" in Progress Software's Programming Handbook documentation.
 

Maintaining Context

In "stateless" mode, the WebSpeed or AppServer agent starts a new copy of the program each time, so variable and temp-table contents are not retained between interactions with the client. In other words, "context" is not maintained within the host program the way it is in traditional Progress character-mode programs.

In fact, on a host system with two or more agents running, the agent which handles one client host task may not be the same agent that handles the next host task from that same client. (It depends on the timing of host tasks from other clients.) This means that, if knowledge of previous client actions is needed, the host must have some other way to get this context information.

It also makes global shared variables and other shared objects useless as a way of storing context information for all programs to use for the life of a session.

However, if the ".p" being run needs to create new shared variables so that existing legacy programs can be run without changing them, then those "new shared" objects must be put into the "common include" of the ".p" which is generated by Jargon Writer. They must also be assigned values in EACH host task. A common way to do this is to write an internal procedure within the common include that populates the new shared variables that are also defined there, and then run this internal procedure as the first step within each "business logic" include file associated with each host task’s internal procedure.

This "common include" is the name that you assign to a Progress include file, such as "MyProgram.i", which will be included in the generated source program at the "top" level (the outer or main procedure of the program), not within an internal procedure. This is where you can define new shared objects, temp-tables, functions and internal procedures so that they are accessible to all other internal procedures in the program. A Progress compiler limitation does not allow these objects to be defined within an internal procedure, in any event, so they have to be defined at the outer "main" procedure level within a ".p" program.

In general, there are three possible ways to manage context when using an n-tier architecture that has middleware like WebSpeed or AppServer in the middle tier. Each approach has different benefits and drawbacks.

1. Store context on the host.

With this approach, an application will store context information in a database table or a text file and associate it with a User ID or session ID that is sent by the client with each interaction. Upon initial login, the context record or file is created and uniquely keyed to this user session.

Jargon Building Blocks uses a Progress sequence counter to assign a unique integer for a session ID, and has a database table with the session ID as a unique key. This table also contains several session status fields, and one character field into which all session context can be stored (in a delimiter-separated list of name/value pairs).

If a lot (over 30KB) of context info is needed, additional tables would probably be needed, keyed by session ID and some secondary key such as a record number for context that consists of temp-tables.

Then, on each following host task that the user runs, such as when a "submit" or "next" button is clicked, the userid and session id values are sent from the client along with any other host parameters. The host program then, as its first step, uses these keys to read the database record or disk file associated with these keys, and populates variables (possibly new shared variables) within itself before doing any business logic. At the end of the host task, if any new or changed context information now exists, the context data has to be rewritten back to the record or file.

Also, a common approach is to present the user with a login screen (User ID and password) when the client application starts, followed by a host task which validates the user against a database table. Once approved, these User ID and password values must be sent as parameters with each subsequent host task and then revalidated by the host code for proper security. These parameters are usually sent by getting their values from the (hidden) login screen fields where they were entered. (The login app is still in memory, but it is no longer visible).

This approach requires more host-side coding and can be slower than other techniques due to the disk reads and writes, but may become necessary if there is a large amount of context to be saved between interactions (especially open-ended temp tables, etc.).

2. Store context on the client. With this approach, an application will store context information by sending all context information to the client whenever it is created or changed by the business logic on the host. Then, the client sends all context required by the host procedure as host parameters on every host task. At a minimum, this will always be done with the User ID and password, to ensure security checking is done on each host task.

On the client, context is normally stored in textfields that are grouped under a non-visible "Misc Container" component. These become like working variables in a Progress program.

This approach is simplest and has the best performance, as long as a relatively small number of values are being passed back and forth each time. "Small" is a subjective term, but certainly a few dozen values are no problem, and 25-50 could be reasonable.

However, if the host generates an open-ended number of records in a table as context, and the size could be in the thousands, it may not be an appropriate method to store such context on the client, both because of memory requirements on the client and because of data transmission times between client and host.

3. Don’t use a stateless model. With this approach, the application gives up the benefits of stateless mode and instead uses a state-aware mode. This ties up one webspeed agent or appserver agent for EACH concurrent user, from the time a session starts until the user logs out. If there could be 100 users online at the busiest time of day, then the site would need a 100-agent webspeed license or a 100-user appserver license.

This technique is NOT recommended for general design, but may be appropriate in very limited situations, and only for temporary use during certain procedures within an application.

By contrast, if you design for a stateless model, you can expect to be able to support roughly 5 active users per agent. This is only a guideline and is very application-dependent, as you might imagine. Here an "active" user means one who is steadily calling up records and changing them, or entering new transactions, such as the traditional "data entry" person. If you are dealing with users who only make occasional host requests or updates, then you could support many more users per webspeed or appserver agent.
 

Record Locking

There are some additional considerations to keep in mind when using a stateless model. One is that, since the agent is not locked between host tasks, there is also no record locking between tasks. This means that potentially, a second user could modify a record between the time that the first user calls up that record, types in some changes, and clicks the "Apply" button to do a second host task to update the record in the database table.

One way to prevent this problem (of users overwriting each others’ updates) is to keep a "last changed" date/time stamp in each DB record. This can be sent to the client when the data from the record is displayed, and then sent back to the host with the other data fields when the record is being updated.

Then, if the date/time stamp on the record is different than what the client is sending, it means that someone else has squeezed in a change while the first user was typing away on their keyboard. And, the action would be to send a message to the first user that they need to refresh the record display and then re-enter their changes.

This may not be ideal, but it shouldn’t happen too often in a "normal" application, and it is a lot better than the alternatives of using state-aware agents and locking database records for possibly long periods of time.


 Up to Top     Return to Help Index