|
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 vs. Procedure-Driven Flow of Execution
-
Maintaining context between user requests
-
Record locking strategies
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.
|