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:

Design Steps

When creating a new client or server application:

  1. Analyze your application requirements.

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.

  1. Define your data protocol.
  2. 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.

  3. Determine if your application should use the default Message and Event handlers.
  4. 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.

  5. Determine of your application should derive from RTRClassFactory.

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.

Implementation Steps

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.

Implementing a Server

To implement a server application, you:

For example, the typical steps for implementing a server are the following:

  1. Create an environment for the application to run by registering a partition for the server.

// 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.

  1. Instantiate the RTRServerEventHandler and RTRServerMessageHandler classes.

For example,

SimpleServerEventHandler *pEventHandler = new
SimpleServerEventHandler();

SimpleServerMessageHandler *pMessageHandler = new
SimpleServerMessageHandler();

  1. Create an RTRServerTransactionController to receive incoming messages and events from a client.

For example,

RTRServerTransactionController *pTransaction = new
RTRServerTransactionController();

  1. Register the partition and both handlers with the transaction controller.

For example,

sStatus = pTransaction->RegisterHandlers( pMessageHandler,

pEventHandler );

assert(RTR_STS_OK == sStatus);

  1. Create a RTRData pointer. This pointer is assigned a pointer to a message or event when RTRSoerverTransactionController::Receive is called.

For example,

RTRData *pDataReceived = NULL;

  1. Create a control loop to continually receive messages and dispatch them to the message and event handlers.
  2. For example,

    while (true)

    {

    sStatus = pTransaction->Receive(pDataReceived);

    print_status_on_failure(sStatus);

    sStatus = pDataReceived->Dispatch();

    print_status_on_failure(sStatus);

    }

  3. Accept the Transaction when your business logic succeeds. When the server has successfully finished its work, tell RTR that it is willing to accept the transaction.
  4. 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();

    }

  5. Acknowledge the outcome of the transaction. A server must tell RTR that it has received the outcome of the transaction. This explicitly tells RTR that it is ok for this Transaction Controller to process the next transaction.

The default behavior in RTRServerMessageHandler::OnAccepted is to acknowledge the outcome of the transaction.

void ABCSHandlers::OnAccepted( RTRMessage *pRTRMessage, RTRServerTransactionController *pController )

{ pController->AcknowledgeTransactionOutcome();

return;

}

Implementing a Client

To implement a client application, you:

In more detail:

  1. Create a ClientTransactionController to receive the incoming messages and events from a server.
  2. For example,

    RTRClientTransactionController *pTransaction = new
    RTRClientTransactionController();

  3. Register the facility with the TransactionController object.
  4. For example,

    sStatus = RegisterFacility(ABCFacility);

    print_status_on_failure(sStatus);

    if(RTR_STS_OK == sStatus)

    {

    m_bRegistered = true;

    }

  5. Create an RTRApplicationMessage derived class that adds to the Data object the information that is to be sent to the server. Usually the data is added within the derived class by calling RTRStream::WriteToStream.
  6. class MyApplicationMessage : public RTRApplicationMessage

    MyApplicationMessage *pMessage1 = new MyApplicationMessage()

  7. Send a message to the server.
  8. For example,

    sStatus = pTransaction->SendApplicationMessage(pMessage1);

    print_status_on_failure(sStatus);

  9. Accept the transaction. When the application has successfully finished the transaction, the client tells RTR that it votes to accept the transaction.

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).

Implementation Example

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.

Examining RTRData Objects

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;

Sample Server Application

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.

Processing Method

The sample server application implements the event-driven processing model in ProcessIncomingTransactions. Implementation of ProcessIncomingTransactions is as follows:

  1. Create a transaction controller to receive incoming messages and events from a client.
  1. Create an environment where the server can run, then Register with RTR the partitions, handler classes, class factory and objects using the transaction controller:
  2. 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);

    }

  3. Instantiate the handler class ABCSHandlers.
  4. Create an RTRData object to hold each incoming message or event. This object will be reused.
  5. // Start processing orders.

    abc_status sStatus;

    RTRData *pOrder = NULL;

  6. Continually loop, receiving messages and dispatching them to the handlers:
  7. 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);

    ...

    }

  8. Check to see if there were any problems processing the order. If so, let the handler know that this transaction is to be rejected when asked to vote.
  9. void ABCOrderProcessor::CheckOrderStatus (abc_status sStatus)

    if (sStatus == ABC_STS_ORDERNOTPROCESSED)

    {

    // Let the handler know that the current txn should be rejected

    GetHandler()->OnABCOrderNotProcessed();

    };

  10. Cleanup. Delete this order that was allocated by the class factory. In the sample application, the class factory returns a separate instance of an order each time it is called.

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().

Sample Client Application

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

  1. Create the environment where ABCOrderTaker is to run by registering a facility:
  2. sStatus = RegisterFacility(ABCFacility);

    print_status_on_failure(sStatus);

    if(RTR_STS_OK == sStatus)

    {

    m_bRegistered = true;

    }

  3. Create a Transaction Controller to receive incoming messages and events from a client.
  4. ABCOrderTaker OrderTaker;

  5. Send the server a message:
  6. sStatus = SendApplicationMessage(pOrder);

    print_status_on_failure(sStatus);

  7. Since we have successfully finished our work, tell RTR that we are willing to accept the transaction. Let RTR know that this is the object being sent and that we are done with our work:
  8. sStatus = AcceptTransaction();

    print_status_on_failure(sStatus);

  9. Determine if the server successfully processed the request

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.

Defining a Message Format

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.