Reliable Transaction Router
C++ Foundation Classes


Previous Contents Index


Chapter 2
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:

2.1 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.
  2. Define your data protocol.
    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.
    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.
  4. Determine if 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.

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

2.2.1 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.
  2. Instantiate the RTRServerEventHandler and RTRServerMessageHandler classes.


        SimpleServerEventHandler *pEventHandler = new 
                                SimpleServerEventHandler(); 
        SimpleServerMessageHandler *pMessageHandler = new 
                                SimpleServerMessageHandler(); 
    

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


        RTRServerTransactionController *pTransaction = new 
                                 RTRServerTransactionController();  
    

  4. Register the facility, partition and both handlers with the transaction controller.


        ....sStatus = pTransaction->RegisterFacility( pFacilityName ); 
        assert(RTR_STS_OK == sStatus); 
        ....sStatus = pTransaction->RegisterPartition( pPartitionName ); 
        assert(RTR_STS_OK == sStatus); 
            sStatus = pTransaction->RegisterHandlers( pMessageHandler, 
                                                      pEventHandler ); 
        assert(RTR_STS_OK == sStatus); 
    

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


        RTRData *pDataReceived = NULL; 
    

  6. Create a control loop to continually receive messages and dispatch them to the message and event handlers.


    while (true) 
        { 
        sStatus = pTransaction->Receive(pDataReceived); 
        print_status_on_failure(sStatus); 
        sStatus = pDataReceived->Dispatch(); 
        print_status_on_failure(sStatus); 
        } 
    

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


        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(); 
            } 
    

  8. 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; 
        } 
    

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


    RTRClientTransactionController *pTransaction = new 
                                    RTRClientTransactionController(); 
    

  2. Register the facility with the TransactionController object.


    sStatus = RegisterFacility(ABCFacility); 
            print_status_on_failure(sStatus); 
    if(RTR_STS_OK == sStatus) 
            { 
               m_bRegistered = true; 
            } 
    

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


        class MyApplicationMessage : public RTRApplicationMessage 
        MyApplicationMessage *pMessage1 = new  MyApplicationMessage() 
    

  4. Send a message to the server.


        sStatus = pTransaction->SendApplicationMessage(pMessage1); 
        print_status_on_failure(sStatus); 
    

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


        pTransaction->AcceptTransaction(); 
    

    The client application's business logic operates between sending a first message to the server and accepting the transaction (Step 5).

2.2.3 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:

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; 
    } 

2.3 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


2.3.1 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:

2.3.2 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 ABCOrder data class. This class adds functionality to the RTRApplicationMessage class by defining three additional methods.

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.

2.3.3 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 RTRApplicationMessage.

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.


Previous Next Contents Index