Design and Implementation
This chapter contains suggestions for designing and implementing a new client or server application using the C++ foundation classes. It also includes code examples from the C++ bookorder and processing sample application included in the examples directory of the RTR kit.. This sample application shows how to implement a derived-receive model. Topics include:
When creating a new client or server application:
Consider your business functions and map them to C++ classes. In the sample application, the client application accepts orders to purchase books, and the server application processes these orders from the client application.
The data protocol defines the data that is passed between client and server applications. In the sample application, orders are passed between client and server. These orders can be books or magazines. Book is a type of Order.
A properly designed RTR application must handle all the possible messages and events that it may receive. To make this task easier Handler classes are provided, RTRServerMessageHandler and RTRServerEventHandler. These two classes provide a separate method for each potential message and event that an application may receive. The methods provide a default implementation for the application.
Most applications will benefit from using the default handlers. Using these handlers simplifies your design by allowing you to derive your own handlers from the default handlers and override only the messages and events which are of interest to your application. The messages and events, which are not overridden, are processed using the default implementation supplied in the base class.
Review: The Receive() method on a Transaction Controller returns an object derived from RTRData. This may be a Message or Event sent by the application or RTR itself. To process this unknown message or event the application simply needs to call the Dispatch() method on the RTRData derived object.
In rare situations an application may decide that it does not wish to use handlers. Unless a handler is registered with the Transaction controller it will not be used. In this case calling Dispatch would return an error.
When the Receive() method of a Transaction Controller is called RTR needs to create an object derived from RTRData to hold the data being received. More specifically, it creates one of the following objects:
An application may wish to have its own object returned when Receive is called. This is easily achieved by registering its own class factory object, which is derived from RTRClassFactory. RTR will call the appropriate method in the class factory and the application may return its own class, which is derived from the base class being created. This allows the application great flexibility when processing incoming data.
Many applications will find it very valuable to derive their own class(es) from RTRApplicationMessage and return instances of this class from their custom class factory.
RTR calls the CreateRTRApplicationMessage() method of the class factory with the data being received. This allows the application to parse the data before it is received and return the correct object for the application. For example, the sample application looks at the application message being received, determines if it is receiving a book or magazine and returns an instance of the correct object.
In some circumstances an application may always pass only one type of data, in this case it may chose not to register a class factory.
The steps described in this section for client and server applications implement a polling client application and an event-driven server application. These steps include code examples that are part of the book processing sample application for ordering books and magazines.
While the steps in this section are representative of client and server applications, there are design alternatives. A sampling of these design alternatives is provided in later sections of this chapter.
To implement a server application, you:
For example, the typical steps for implementing a server are the following:
// Create a partition that processes ISBN numbers in the
// range 0-99
unsigned int low = 0;
unsigned int max = 99;
RTRKeySegment KeyZeroTo99( rtr_keyseg_unsigned,
sizeof(int),
0,
&low,
&max );
RTRPartitionManager PartitionManager;
sStatus = PartitionManager.CreateBackendPartition( ABCPartition1,
ABCFacility,
KeyZeroTo99,false,true,false);
print_status_on_failure(sStatus);
While the above example shows only the RTR_STS_OK return value, typical applications must check for other status returns.
For example,
SimpleServerEventHandler *pEventHandler = new
SimpleServerEventHandler();
SimpleServerMessageHandler *pMessageHandler = new
SimpleServerMessageHandler();
For example,
RTRServerTransactionController *pTransaction = new
RTRServerTransactionController();
For example,
sStatus = pTransaction->RegisterHandlers( pMessageHandler,
pEventHandler );
assert(RTR_STS_OK == sStatus);
For example,
RTRData *pDataReceived = NULL;
For example,
while (true)
{
sStatus = pTransaction->Receive(pDataReceived);
print_status_on_failure(sStatus);
sStatus = pDataReceived->Dispatch();
print_status_on_failure(sStatus);
}
RTRServerTransactionController * pController;
pController->AcceptTransaction();
Note the default behavior supplied by the OnPrepareTransaction method of the RTRServerMessage handler is to call AcceptTransaction on behalf of the application.
The sample application overrides this default behavior to reject the transaction if the order could not be processed.
void ABCSHandlers::OnPrepareTransaction( RTRMessage *pRTRMessage, RTRServerTransactionController *pController )
// Check to see if anything has gone wrong. If so, reject the
// transaction, otherwise accept it.
if (true == m_bVoteToAccept)
{
pController->AcceptTransaction();
}
else
{
pController->RejectTransaction();
}
The default behavior in RTRServerMessageHandler::OnAccepted is to acknowledge the outcome of the transaction.
void ABCSHandlers::OnAccepted( RTRMessage *pRTRMessage, RTRServerTransactionController *pController )
{ pController->AcknowledgeTransactionOutcome();
return;
}
To implement a client application, you:
In more detail:
For example,
RTRClientTransactionController *pTransaction = new
RTRClientTransactionController();
For example,
sStatus = RegisterFacility(ABCFacility);
print_status_on_failure(sStatus);
if(RTR_STS_OK == sStatus)
{
m_bRegistered = true;
}
class MyApplicationMessage : public RTRApplicationMessage
MyApplicationMessage *pMessage1 = new MyApplicationMessage()
For example,
sStatus = pTransaction->SendApplicationMessage(pMessage1);
print_status_on_failure(sStatus);
For example,
pTransaction->AcceptTransaction();
The client application's business logic operates between sending a first message to the server and accepting the transaction (Step 5).
The following server application example shows the steps in setting up the infrastructure for running an application to process transactional requests. There is a server.h and a server.cpp file.
In the server.h file, after including the necessary header files and defining pointers to RTR facility and partition names, the business class, deriving from the RTRServerTransactionController class is defined. This includes declaring a transaction controller constructor and destructor.
#include <iostream.h>
#include <rtrapi.h>
#include <assert.h>
const char *ABCFacility = "MyFacility";
const char *ABCPartition = "MyPartition";
class SRVTransactionController: public RTRServerTransactionController
{
public:
SRVTransactionController();
~SRVTransactionController();
private:
};
SRVTransactionController::SRVTransactionController()
{
cout << "In Server Transaction Controller constructor " << endl;
}
SRVTransactionController::~SRVTransactionController()
{
cout << "In Server Transaction Controller destructor " << endl;
}
The server message and event handlers are then declared and defined. MySRVMessageHandler derives from RTRServerMessageHandler and MySRVEventHandler derives from RTRServerEventHandler. In this example, the RTRServerMessageHandler methods OnAccepted, OnPrepareTransaction and the RTRServerEventHandler method OnServerIsPrimary are overridden. Both handler classes also define constructors and destructors:
class MySRVMessageHandler: public RTRServerMessageHandler
{
public:
MySRVMessageHandler();
~MySRVMessageHandler();
rtr_status_t OnPrepareTransaction( RTRMessage *pmyMsg,
RTRServerTransactionController *pTC);
rtr_status_t OnAccepted( RTRMessage *pmyMsg,
RTRServerTransactionController *pTC);
private:
};
MySRVMessageHandler::MySRVMessageHandler()
{
}
MySRVMessageHandler::~MySRVMessageHandler()
{
}
rtr_status_t MySRVMessageHandler::OnPrepareTransaction(
RTRMessage *pmyMsg,
RTRServerTransactionController *pTC)
{
cout << "prepare txn " << endl;
pTC->AcceptTransaction();
return RTR_STS_OK;
}
rtr_status_t MySRVMessageHandler::OnAccepted(
RTRMessage *pmyMsg,
RTRServerTransactionController *pTC)
{
cout << "accepted txn " << endl;
pTC->AcknowledgeTransactionOutcome();
return RTR_STS_OK;
}
class MySRVEventHandler: public RTRServerEventHandler
{
public:
MySRVEventHandler();
~MySRVEventHandler();
rtr_status_t OnServerIsPrimary( RTREvent *pRTREvent,
RTRServerTransactionController *pTC );
};
MySRVEventHandler::MySRVEventHandler()
{
}
MySRVEventHandler::~MySRVEventHandler()
{
}
MySRVEventHandler::OnServerIsPrimary( RTREvent *pRTREvent,
RTRServerTransactionController *pTC )
{
cout << "This server is primary " <<endl;
return RTR_STS_OK;
}
In the server.cpp file, after including the server.h file and instantiating the SRVTransactionController class (myTC), the management class steps for setting up the RTR infrastructure take place. These steps create the RTR environment for client and server transactional messaging. This includes:
#include "srv.h"
int main(void)
{
rtr_status_t sStatus;
SRVTransactionController myTC;
// start rtr
RTR myRTR;
sStatus = myRTR.Start();
cout << myRTR.GetErrorText(sStatus) << endl;
// create journal
sStatus = myRTR.CreateJournal(true);
cout << myRTR.GetErrorText(sStatus) << endl;
// create facility
RTRFacilityManager myFac;
// get nodes names for facility
char *pszBackendNodes = "dejavu";
char *pszRouterNodes = "dejavu";
char *pszFrontendNodes = "dejavu";
char *nodename = "dejavu";
sStatus = myFac.CreateFacility(ABCFacility,pszRouterNodes,
pszFrontendNodes,pszBackendNodes,false,false);
cout << myRTR.GetErrorText(sStatus) << endl;
RTRPartitionManager myPartition;
char *low="A";
char *high="Z";
RTRKeySegment mySegment(rtr_keyseg_string,1,
0,low,high);
sStatus = myPartition.CreateBackendPartition(ABCPartition,
ABCFacility,mySegment,false,true,true);
cout << myRTR.GetErrorText(sStatus) << endl;
Then register the facility, partition and handler classes and instantiate a pointer to a data object (*myData).
sStatus = myTC.RegisterFacility(ABCFacility);
cout << myRTR.GetErrorText(sStatus) << endl;
sStatus = myTC.RegisterPartition(ABCPartition);
cout << myRTR.GetErrorText(sStatus) << endl;
MySRVMessageHandler myHandler;
MySRVEventHandler myEventHandler;
myTC.RegisterHandlers(&myHandler,&myEventHandler);
RTRData *myData;
Finally, create control loop logic with the Receive and Dispatch methods.
while(true)
{
sStatus = myTC.Receive(&myData);
cout << "message received " << myRTR.GetErrorText(sStatus) <<
endl;
if ( sStatus != RTR_STS_OK)
{
assert(false);
}
sStatus = myData->Dispatch();
cout << myRTR.GetErrorText(sStatus) << endl;
delete myData;
}
cout << "hey I am done" <<endl;
return 0;
}
Sample Application Walkthrough
This section uses the sample application included in the RTR kit as an example of implementing both a client and a server application using the C++ foundation classes.
The sample application is a simple client and server for ordering books and magazines.
The client takes orders and creates the corresponding Book or Magazine object. This object is told to serialize itself (write its state to a stream) and the client then sends the serialized object to a server.
The server application creates and registers two partitions. These partitions represent orders with ISBN numbers from 1-99 and 100-199. The server will register a custom class factory to peek at the object, which it is about to receive and determine its type, book or magazine. When the object has been created by the class factory and returned to the application the server will tell the object to deserialize itself and then to process itself. Processing means to carry out the business logic of buying the book or magazine.
The sample application demonstrates the following features:
In this sample there are three server classes and one client class. Each class is declared in its own .h file and implemented in a .cpp file.
The server classes are:
The client classes are:
There are three common data classes:
Figure 2-1 illustrates the messaging between the sample client and server applications.
Figure 2-1: Sample Application Messaging
Deriving from Base Classes in the Sample Application
This section provides examples of creating derived classes in the book-ordering sample application for implementing additional functionality in client and server application code by:
Adding Functionality to Data Objects
You can add functionality to an RTRData object without changing any code in the Message or Event handlers or the Receive loop, by deriving a class from RTRData.
Figure 2-2 illustrates the base class relationships to the ABCBook, ABCMagazine, and ABCOrder data classes. The ABCOrder class adds functionality to the RTRApplicationMessage class by defining three additional methods. These three methods are overridden in the ABCBook and ABCMagazine classes.
Figure 2-2: Adding Functionality to RTRData
For example, a book is represented as an ABCBook object with its inherited Dispatch method from ABCOrder. This class overrides the WriteObject, ReadObject, and Process methods. A magazine is represented as an ABCMagazine object with overridden WriteObject ReadObject, and Process methods, and the Dispatch method inherited from ABCOrder.
Encapsulating Data with RTRData
The following example illustrates the protocol class that encapsulates application-level data with an RTRData-derived class. In this sample application, two kinds of orders are processed by the server application, book orders and magazine orders. An order is defined as an ABCOrder object which derives from RTRApplicationMessage. All data sent between the client and server applications represents either a magazine order or a book order. As Figure 2-3 shows, there are two kinds or orders, book orders and magazine orders. This information is represented in a buffer organized for sending to the server from the client.
Figure 2-3: Encapsulating Data with RTRData
These two classes have been derived from the application's base class, ABCOrder. Book and Magazines are kinds of Orders. The order class tells its derived classes when to serialize their data. When this happens, the data in stored in the RTRData class via the methods of the RTRStream class.
When the client application is to make a request, the user enters the data for the fields illustrated above. The client application then stores this information in the corresponding book or magazine object and sends it to the server using SendOrder. The server then calls Receive to obtain the Book or Magazine order. Note that a Book (or magazine) is an RTRApplictionMessage..
In addition to RTRApplicationMessage data objects, three other kinds of RTR data can exist in the RTR application:
The application must be set up to handle these data classes, even if an application chooses to ignore them. In the sample application, if an order is an RTRApplicationMessage, then the object (an order) is processed by the Dispatch method. If the data is an RTRMessage or an RTREvent, then default handling occurs, and the event and message handler methods are called. The default Dispatch methods then execute, as each RTRData-derived data class has its own Dispatch method.
When ABCOrderProcessor calls its derived Receive method, one of the four types of data objects is assigned. The server can receive RTRMessage and RTREvent or can overwrite code in the class factory class to receive book or magazine orders. The class factory returns a pointer to incoming data (as an RTRData pointer) and knows what kind of object to return.
You can check the contents of an RTRData object by calling any RTRData method such as IsMessage. The following example from the client application ABCOrderTaker illustrates how an application can retrieve and use the message from an RTRData derived object.
while (OrderBeingProcessed == eTxnResult)
{
sStatus = Receive(&pResult);
print_status_on_failure(sStatus);
if ( true == pResult->IsRTRMessage())
{
// Check to see if we have a status for the transaction.
// rtr_mt_accepted = Server successfully processed our request.
// rtr_mt_rejected = Server could not process our request.
sStatus = ((RTRMessage*)pResult)->GetMessageType(mtMessageType);
print_status_on_failure(sStatus);
if (rtr_mt_accepted == mtMessageType) return eTxnResult = OrderSucceeded;
if (rtr_mt_rejected == mtMessageType) return eTxnResult = OrderFailed;
}
}
return eTxnResult;
The following figure illustrates the objects within the ABCOrderProcessor server application. Each of the four server classes derives from the associated base classes.
Figure 2-4: Sample Server Application
The implementation of ABCOrderProcessor uses default construction and destruction and then follows the steps described earlier in this chapter to create a server application.
The sample server application implements the event-driven processing model in ProcessIncomingTransactions. Implementation of ProcessIncomingTransactions is as follows:
sStatus = RegisterFacility(ABCFacility);
print_status_on_failure(sStatus);
// ABC Partition
sStatus = RegisterPartition(ABCPartition1);
print_status_on_failure(sStatus);
sStatus = RegisterPartition(ABCPartition2);
print_status_on_failure(sStatus);
// ABC Class Factory
sStatus = RegisterClassFactory(&m_ClassFactory);
print_status_on_failure(sStatus);
// ABC Handlers
sStatus = RegisterHandlers(&m_rtrHandlers,&m_rtrHandlers);
print_status_on_failure(sStatus);
return;
// Create the environment:
void ABCOrderProcessor::CreateRTREnvironment()
{
rtr_status_t sStatus;
// If RTR is not already started then start it now.
StartRTR();
// Create a Facility if not already created.
CreateFacility();
// Create a partition that processes ISBN numbers in the range 0 - // 99
unsigned int low = 0;
unsigned int max = 99;
RTRKeySegment KeyZeroTo99( rtr_keyseg_unsigned,
sizeof(int),
0,
&low,
&max );
RTRPartitionManager PartitionManager;
sStatus = PartitionManager.CreateBackendPartition( ABCPartition1,
ABCFacility,
KeyZeroTo99,
false,
true,
false);
print_status_on_failure(sStatus);
// Create a partition that processes ISBN numbers in the range 100 - // 199
low = 100;
max = 199;
RTRKeySegment Key100To199( rtr_keyseg_unsigned,
sizeof(int),
0,
&low,
&max );
sStatus = PartitionManager.CreateBackendPartition( ABCPartition2,
ABCFacility,
Key100To199,
false,
true,
false);
print_status_on_failure(sStatus);
}
// Start processing orders.
abc_status sStatus;
RTRData *pOrder = NULL;
while (true)
{
// Receive an Order
sStatus = Receive(&pOrder);
print_status_on_failure(sStatus);
if(ABC_STS_SUCCESS != sStatus) break;
// if we can't get an Order then stop processing.
// Dispatch the Order to be processed
sStatus = pOrder->Dispatch;
print_status_on_failure(sStatus);
// Exception handling:
// Check to see if there were any problems processing the order.
// If so, let the handler know to reject this transaction when
// asked to vote.
CheckOrderStatus(sStatus);
...
}
void ABCOrderProcessor::CheckOrderStatus (abc_status sStatus)
if (sStatus == ABC_STS_ORDERNOTPROCESSED)
{
// Let the handler know that the current txn should be rejected
GetHandler()->OnABCOrderNotProcessed();
};
delete pOrder;
Server Message and Event Handler
The ABCOrderProcessor server application includes the derived class ABCSHandler for event-driven message and event handling. As Figure 2-5 illustrates, it combines both handlers into one handler class by deriving from both RTRServerEventHandler and RTRServerMessageHandler classes.
Figure 2-5: Sample Server-Handler-Derived Class
The ABCSHandler class overrides the following four handler methods:
It uses the default handler methods for:
In addition to the above over-ridden methods, it also contains an application-defined method to handle exceptions, OnABCOrderNotProcessed().
Figure 2-6 illustrates the ABCOrderTaker sample application. This example uses the polling receive processing model, not message or event handlers.
Figure 2-6: Sample Client Application
The client application header file ABCOrderTaker.h declares the interface for the ABCOrderTaker class. The file ABCOrderTaker.cpp provides the implementation.
In addition to the default constructor and destructor, there are two methods within class ABCOrderTaker:
SendOrder
sStatus = RegisterFacility(ABCFacility);
print_status_on_failure(sStatus);
if(RTR_STS_OK == sStatus)
{
m_bRegistered = true;
}
ABCOrderTaker OrderTaker;
sStatus = SendApplicationMessage(pOrder);
print_status_on_failure(sStatus);
sStatus = AcceptTransaction();
print_status_on_failure(sStatus);
eTxnResult = DetermineOutcome();
return true;
RTR Applications in a Multiplatform Environment
Applications using RTR in a multiplatform (mixed endian) environment with non-string application data must tell RTR how to marshall the data both for the destination of the application data being sent and the application data itself. This description is supplied as the rtr_const_msgfmt_t argument to:
The default (that is, when rtr_const_msgfmt_t is supplied) is to assume the application message is string data.
The rtr_const_msgfmt_t string is a null-terminated ASCII string consisting of a number of field-format specifiers:
[field-format-specifier, ...]
The field-format specifier is defined as:
%[dimension]field-type
where:
Field |
Description |
Meaning |
% |
Indicates a new field description is starting. |
|
dimension |
Is an optional integer denoting array cardinality (default 1). |
|
field-type |
Is one of the following codes: UB SB UW SW UL SL C UC B |
8 bit unsigned byte 8 bit signed byte 16 bit unsigned 16 bit signed 32 bit unsigned 32 bit signed 8 bit signed char 8 bit unsigned char boolean |
For example, consider a data object containing the following:
unsigned int m_uiISBN;
unsigned int m_uiPrice;
char m_szTitle[ABCMAX_STRING_LEN];
char m_szAuthor[ABCMAX_STRING_LEN];
The rtr_const_msgfmt_t for this object could be ("%UL%SL%12C%12C").
The transparent data-type conversion of RTR does not support certain conversions (for example, floating point). These should be converted to another format such as character string.