15.3. The Message

 <  Day Day Up  >  

In this library system, the underlying mechanism for communication is the Message . A Message contains a header with a message identifier, and parameter values or response values corresponding to the message type. Each operation performed by a print client or a release station client is transformed into one or more Message s that are sent to the central server. The server responds to each Message with a Message back to the client. The client takes action appropriate to the operation and the response. It might display information on the GUI, report an error, or continue to send other Message s. Message s are transmitted between the two systems in text ("To Text or Not to Text"). The Message class has a well-defined interface ("Create Interface Contracts") and looks like this:

 class Message         ComputerID computer_id         UserID user_id         Timestamp timestamp         abstract Integer get_message_type_id(  )         abstract convert_from_text(String text)         abstract String convert_to_text(  )         abstract Message handle(  ) 

computer_id identifies the source of the message. user_id identifies the user at the client. timestamp is used for logging and tracking purposes. The convert_from_text( ) and convert_to_text( ) methods convert the attributes in derived Message s to and from text. The handle( ) method processes the message. get_message_type_id( ) returns the type identifier assigned to each message type.

15.3.1. Particular Messages

Every message is derived from Message and includes the attributes for a particular type of message. Each derived Message overrides the four methods in the base class. The organization of each message looks like ParticularMessage :

 class   ParticularMessage   extends Message         // Attributes for the message         // Overrides of abstract methods 

MessageEncoderDecoder converts the entire Message from and to text:

 class MessageEncoderDecoder         Message decode_from_text(String text)         String encode_to_text(Message a_message) 

The decode_from_text( ) method decodes the header text, determines the message type, and creates the corresponding message. Then it calls convert_from_text( ) to convert the remainder of the text to attribute values for a ParticularMessage . encode_to_text ( ) works in reverse to create the header text and calls convert_to_text( ) for the corresponding message.

The actual format of the textual representation is hidden behind this interface. It could be in comma-delimited form, XML or a custom format The text is used just to communicate between the systems. The only consideration in the underlying textual representation is that all clients and servers need to agree on it. It is possible for a server to understand two different formats of messages. It just results in a little more code on the interpretation side. Since the clients were not being rewritten, we used the format that was on the current system. Vertical bars separate the text representing each attribute.

We could convert the messages to and from text using a common method that takes a table instead of creating executable code in each convert_to_text( ) and convert_from_text( ) method ("Declaration over Execution"). The implementation of the table is language dependent. However, if all message attributes were declared as an abstract data type (with corresponding to_string( ) and from_string( ) methods), the implementation could be relatively simple.

15.3.2. Handling a Message

The handle( ) method for a ParticularMessage does all the work using the attributes of the ParticularMessage . The method is invoked by a series of communications from the GUI through the network and to the server, as shown in Figure 15-1 ("Separating Concerns Makes Smaller Concerns"). Here are the interfaces and classes that work together to perform an operation requested by the client and return a response:

 interface MessageReceiver         Message process_message(Message message_to_process)     interface TextSender         String send_text(String text_to_send)     interface TextReceiver         String receive_text(String text_to_receive)     interface Operation         Message   do_particular_operation   (  )     class ClientMessageDispatcher implements MessageReceiver         Message process_message(Message message_to_process)     classClientSocket implements TextSender         String send_text(String text_to_send)     class ServerSocket         Implements a receiving socket     class ServerMessageReceiver implements TextReceiver         String receive_text(String text_to_receive)     class ServerMessageDispatcher implements MessageReceiver         Message process_message(message_to_process) 

Figure 15-1. Sequence diagram for handling a message

The sequence diagram in Figure 15-1 shows the overall flow. The flow details are shown in the following list. When the user commands the GUI to perform a particular operation, the flow is initiated. The GUI calls the appropriate operation method in the Operations interface, shown as do_particular_operation( ) in the example.

  • Operations do_particular_ operation( )

    Creates a Message of the appropriate type with the appropriate values

    Calls ClientMessageDispatcher.process_message( )

    Returns values in response Message back to the caller

  • ClientMessageDispatcher.process_message( )

    Calls MessageEncoderDecoder.encode_to_text( )

    Calls ClientSocket.send_text( ) with encoded text

    Converts received text to Message using MessageEncoderDecoder.decode_from_text( )

    Returns Message

  • ClientSocket.send_text( )

    Sends the text over the network to the server

    Returns received text

  • ServerSocket

    Receives text over the network

    Calls ServerMessageReceiver.receive_text( )

    Sends returned text back over the network to the client

  • ServerMessageReceiver.receive_text( )

    Calls MessageEncoderDecoder.decode_from_text( )

    Calls ServerMessageDispatcher.process_message( )

    Calls encode_to_text( ) for the returned Message

    Returns the text

  • ServerMessageDispatcher.process_message( )

    Calls handle( ) for the Message

    Returns Message

Each class has one simple job to perform. The only classes that need to be aware of particular messages are the methods in the Operation interface and the handle( ) method for each particular message.

15.3.3. Environment

To use this message system on a particular computer, the client and I set up two environment classes: ClientEnvironment and ServerEnvironment . The details in these two classes can be summarized as follows :

 class ClientEnvironment         ComputerInformation (computer name, server IP address)         UserInformation (logged on user id)     class ServerEnvironment         ComputerInformation (computer name)         Database (test or production) 

The client-side classes have a reference to ClientEnvironment and the server-side classes have a reference to ServerEnvironment . By changing the member references in each environment, we have a flexible testing situation ("Build Flexibility for Testing").

15.3.4. Example Messages

Here is a concrete example to help understand the overall flow. When Sandra prints a document on her personal computer, a PrintJob is created and sent to the central server. Sandra then goes to a release station. She enters her UserID. The release station displays her PrintJob s in a dialog box. She selects a job to print. The release station submits a ReleaseJobMessage to request the job to be released. The central server releases the job to the printer and returns a JobPickupMessage that indicates where the printer is located. The messages are as follows:

 class ReleaseJobMessage         Integer jobNumber  // job to release     class JobPickupMessage         String printer_location     class FailMessage         String explanation 

On the release station, the method that sends the ReleaseJobMessage to the server looks like this:

 String release_job_operation(Integer jobNumber)         {         ReleaseJobMessage releaseJobMessage(jobNumber);         MessageReceiver messageReceiver =             ClientEnvironment.getMessageReceiver(  );         Message returnMessage =             messageReceiver.process_message(releaseJobMessage);         if (returnMessage is_type_of JobPickupMessage)             return (JobPickupMessage) Message.printer_location;         else if (returnMessage is_type_of FailMessage)             return (FailMessage) Message.explanation;         else             return ERROR_SERVER_COMMUNICATION;         } 

On the server side, you find the following code, which I've simplified a bit, to handle the ReleaseJobMessage message.

 class ReleaseJobMessage         {         Message handle(  )             {             PrintJob printJob=                 ServerEnvironment.getJobDB(  )->getJobByNumber(jobNumber);             if (printJob == NULL)                 return FailMessage("Print Job Not Found");             else                 {                 printJob.releaseToPrinter(  );                 Printer printer = printJob.printer;                 return JobPickUpMsg(printer.location);                 }             }         } 

 <  Day Day Up  >  


Prefactoring
Prefactoring: Extreme Abstraction, Extreme Separation, Extreme Readability
ISBN: 0596008740
EAN: 2147483647
Year: 2005
Pages: 175
Authors: Ken Pugh

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net