Thepurpose of a request controller is to execute requests by calling thetransaction server programs that can perform the request. If theexecution of the request produces output, the request controller routesthe response back to the front-end program that sent the request.Usually the request controller brackets transactions; that is, itissues the Start, Commit, and Abort operations. Within a transaction,there may be calls to one or more transaction servers.
Arequest may require the execution of many transaction server programsand possibly of many transactions. An application-specific program inthe request controller decides which transaction servers to call and inwhich order. On the face of it, there’s nothing special about it. Itsimply accepts a call that contains a request and calls subroutines(transaction servers) to do most of its work. For requests that executeas a single transaction, the application really is quite simple. Whenmultiple transactions are required to process the request, there arecomplications, but we’ll defer those until Chapter 5.
Mosttransactional middleware products allow the same language to be usedboth for request controller and transaction server functions. So it’sthe application designer’s job to split the request-processing functionbetween the request controller and the transaction server, but it isnot something that is forced on the developer. Nevertheless, this splitis desirable for the reasons discussed in the subsection Multitier Architectures in Section 3.2.
Sometransactional middleware products support a special programminglanguage in which to express the request controller logic. Some ofthese languages were introduced in the 1980s to ensure requestcontroller applications cannot do synchronous I/O, thereby enabling theimplementation of multithreading in middleware (see Section 2.3).Examples are Task Definition Language used in HP’s ACMS (originallyfrom Digital Equipment Corporation) and Screen COBOL used in HP’sPathway TP monitor (originally from Tandem Computers). Otherlanguages are designed to simplify programming multi-transactionrequests, such as the Web Services Business Process Execution Language.This is discussed further in Chapter 5.
Moderntransactional middleware systems handle these functions using acombination of container abstractions, APIs, attributes, andconfiguration properties.
Nomatter what language is used to express it, the request controllergenerally brackets the transaction before it actually calls the programto do the work for the request—that is, it issues a Start operation tobegin a transaction before it calls any transaction server programs.After all the transaction servers that execute on behalf of the requesthave returned, it issues a Commit operation to indicate that thetransaction is done and should be committed. The Start and Commitoperations are issued by the system when using an implicit programmingmodel.
Somediscipline may be required in choosing which programs issue thetransaction bracketing operations Start, Commit, and Abort. Forexample, suppose the transactional middleware or underlying platformdoes not offer a solution to the transaction composability problem,e.g., by properly handling calls to Start issued within a transactionor by using transaction attributes attached to object-orientedcomponents (as in .NET and Java EE). Then the transaction serverprograms should not contain the Start, Commit, and Abort operations,but rather should be pure objects. The request controller that callsthe transaction server program should be the one that actually startsthe transaction and commits or aborts it. That way the callee can becalled from several different applications that use the same procedurein different ways, as described in the subsection Transaction Bracketing in Section 2.2. All the other issues related to the transaction abstraction in Section 2.2 apply here too, such as exception handing, savepoints, and chained versus unchained transaction models.
Onemajor complication related to transaction bracketing is ensuring theintegrity of each transaction’s request message. If a transactionaborts, its request may be lost, making it impossible to re-execute thetransaction, which is very undesirable. The application should catchthe abort exception and return a comprehensible message to the user. Ifit doesn’t, the user might get back an inscrutable error message (e.g.,“transaction aborted” or “HTTP 500: Internal Server Error”), or in somebad cases no response at all.
Toavoid lost requests, the transaction that performs the request shouldinclude the operation that gets a request as input, say Get-input-request as shown in Figure 3.7.Usually, it’s the first operation executed by the transaction. Thesystem should make the Get-input-request operation recoverable. Thatis, if the transaction aborts, then the Get-input-request operation isundone, just like any other recoverable transaction operation. Thus,the request message is again available as input and will cause anothertransaction to execute later, as desired.
Alimit on the number of retries is needed to avoid looping forever onbadly formed requests. If a request is determined to be badly formed,then a transaction should execute Get-input-request, report the error,and commit, thereby ensuring the request doesn’t execute anymore. Asdiscussed in Section 2.2, savepoints can be helpful in structuring this activity.
Usingthe explicit Get-input-request operation, the program waits until afront-end program has an input request to offer. This is commonpractice with dumb display devices, which was the usual situationbefore the advent of PCs.
Theadvent of client-server computing and widespread availability of RPChas made it popular for the client to invoke the server. That is, thefront-end program calls the request controller program, rather thanhaving the request controller call a Get-input-request operation. Inthis case, the operation that receives the client’s call is theinvocation of the called procedure. To avoid the request integrityproblem, this “receive-the-client’s-call” operation must be madeexplicit, so it can be invoked within the context of a transaction andundone if the transaction aborts. The details of how to recover arequest if the transaction aborts are sufficiently complex so that wedevote an entire chapter to them, Chapter 4, and therefore do not discuss them further here.
Therequest controller typically runs in a multithreaded process. Theprocess may be dedicated to request controller functions. Or it may becombined with front-end program functions or transaction serverfunctions, to save context switching overhead. For example, we saw howto combine web server functions with a request controller in Section 3.3, Web Servers.Combining request controller functions in the same process astransaction server functions is usually straightforward. Since arequest controller usually invokes transaction servers using RPC, it issimply a matter of replacing remote procedure calls by local procedurecalls.
Since therequest controller application executes within a transaction, it needsto have a transaction context and to pass that context to transactionservers that it invokes. If the request controller and transactionserver run in separate processes, then this is usually done with atransactional RPC. If they run in the same process, then theyautomatically share thread context and hence transaction context.
Usually,only a modest amount of processing time is required in the requestcontroller to handle each request. Nevertheless, this processing timeis not zero, so even a multithreaded request controller has limitedcapacity. If the system is required to handle a maximum requestcontroller workload that exceeds the processing capacity of a singlemachine, then it may be necessary to partition or replicate the requestcontroller. For example, a request controller could be partitioned byrequest type, and each partition assigned to run on a differentmachine. As with any scale-out scheme, if request controllers arereplicated or partitioned, then it is desirable to have them bestateless with respect to their request sources, to avoid thecomplexity of having to route requests to the particular copy of arequest controller that has the request source’s state.
Onetraditional function of request controllers is to reduce the number ofcommunication sessions by partitioning the sessions that wouldotherwise be required to connect front ends to transaction servers.This helps the session scale-out problem that is described in Section 2.6, Partitioning Sessions. For example, front ends executing in the same geographical location could be serviced by the same request controller.
Morecomplex groupings of connections between front ends and requestcontrollers may be needed for fault tolerance reasons. For example, theATMs at a bank branch may be split across two request controllers overtwo independent communication lines, so the failure of one controlleror communication line still leaves half of the ATMs operating.
Ifthe request controller is receiving requests from untrusted front ends,then requests are a potential source of security threats. The requestcontroller therefore should not trust that the data it receives is wellformed. For example, it should not assume that a buffer it receives isnull terminated or that field A of a message really is the size for field B. The bottom line is that secure coding practices are essential when developing TP applications.
Weassume that the request controller is part of the trusted computingbase. In particular, it is safe to have it be responsible for checkinguser authorization. To do this, it needs to know about theauthenticated user that issued the request. For example, it may need tocheck that the user is authorized to execute the request’s requesttype. It also needs to pass the authenticated user ID to transactionservers, since some access control can be performed only when accessingthe database. For example, permission to withdraw from a bank accountshould be given only to users who own that account.
Inaddition to user authorization, process authorization may be required.For example, a transaction server may be accessible only from requestcontroller processes that are known to have done preliminary userauthorization checks and therefore are trusted. This avoids the needfor the transaction server to duplicate the request controller’s checkthat the user is authorized to execute the request. Processauthorization can be done by associating the request controller processwith an authenticated application administrator, who creates therequest controller process to execute on his or her behalf. Transactionservers are then configured to accept calls only from requestcontroller processes executing on behalf of this applicationadministrator.
Asession may be established between the request controller andtransaction server whose session state includes the authenticatedapplication administrator information. This avoids requiring thetransaction server to perform this security check on every access.Since the authorization state is the same for all requests, thesesessions can be pooled, to avoid creating a new one for every request.
Itcan be time consuming for a system manager to maintain all thissecurity information. It can also be time consuming for thetransactional middleware to check all this security information atruntime. Simplicity of security management and efficiency of runtimesecurity checking are features of transactional middleware products.
联系客服