field-theory.org
the blog of wolfram schroers

The protocol service — a distributed logging facility

The source code can be downloaded as a gzipped .tar-file.

This is the second part of the CORBA tutorial which implements a distributed object system. It exhibits a more involved object design and shows off a couple of more sophisticated IDL expressions of CORBA, including modules, types, attributes, inheritance and exceptions.

The source code has been developed and tested using the mico ORB on MacOS X. On other systems, the Makefile(s) may need to be adjusted properly. The files can also be downloaded on the software section of this site.

Table of contents:

Specification of the service
Defining the interface
Implementation of the service
Implementation of the client
Running the system

Specification of the service

As has been pointed out before, good object design is as much an art as it is a science. The objects we use in the following examples demonstrate a variety of features of CORBA interfaces. However, there are some design decisions we need to make that are peculiar to distributed objects! It is important that you understand the explanation of the problems as well as the features.

The service we will design now will represent an enterprise-wide logging facility. A central server accepts messages from clients and protocols them with date and time stamp. It also features a message counter that can be reset remotely if clients request it. The message counter should always be a positive number, but it is possible to set it to any value without check if a client requests to do so.

With these specifications in mind we can devise an object design for the protocol facility that consists of a remote object with the following interface:

counter
An attribute (instance variable) that contains the current message number. It has appropriate getter and setter functions which the interface exposes.
resetCounter
A separate setter function of the attribute counter that resets it to one.
setCounter
A separate setter function of the attribute counter that sets it to a value other than zero, but makes sure that this value is positive.
setCounterFreely
A separate setter function of the attribute counter that sets it to any value (including negative ones), but may fail and does not report any failure.
echoString
Submits a message to the protocol facility which will be logged with date, time and the current message counter. The message count will be increased by one afterwards.
Protocol facility UML diagram

While it may look a little strange that we have four different setter functions for one attribute (which is certainly overkill), those functions differ in important details and thus illustrate subtle but important difference. It is instructive to understand these differences and their merits and disadvantages.

Defining the interface

Writing the IDL file

With the specification written, we can use the IDL to define the interface in a programming language independent manner. The resulting file protocol.idl looks as follows:

 1 // MICO example program - The Protocol object
 6 
 7 module MICOExample
 8 {
 9   typedef long CounterType;
10 
11   exception NegativeCounter
12   {
13     CounterType badValue;
14   };
15 
16   interface Echo
17   {
18     void echoString(in string input);
19   };
20 
21   interface Protocol : Echo
22   {
23     attribute CounterType counter;
24     const CounterType StartValue = 1;
25 
26     void resetCounter();
27     void setCounter(in CounterType input)
28       raises(NegativeCounter);
29     oneway void setCounterFreely(in CounterType input);
30   };
31 };

What does the IDL file contain?

First, we notice that everything is embedded in a construct denoted module and named MICOExample. A module is similar to a namespace in C++ and helps to group objects of different functionality. Using modules is optional, but in general a good idea. The typedef command defines the type CounterType which is an integer. This is very similar to the corresponding construct in C and C++. The interface then contains an object called exception which has an attribute of CounterType. The exception deals with the situation that the counter is set to a negative value.

The interface Echo is actually identical to the one we already defined before in the previous part. One essential habit of OO design is the reuse of functionality.

At last the Protocol interface is defined, translating the requirements for the different setter functions to IDL. Note that the echoString method is not defined here since the interface is inherited from Echo that already provides this functionality. Thus, we only need to worry about the definition of and the different ways to access the counter attribute.

An additional constant is introduced as StartValue which provides the value that resetCounter sets the counter to when called. Note that there is no explicit getter method and also no standard getter defined – these will be done automatically by the language mapping of IDL!

Notice how the requirements translate to different definitions of setCounter and setCounterFreely. setCounter explicitly declares that it will raise the NegativeCounter exception, whereas setCounterFreely has the keyword oneway in front of it. This keyword forces the function to return immediately when called, discarding any result the operation may cause – even if it fails! It is evident that oneway functions cannot return any data, neither explicitly via a return type argument other than void nor implicitly by having parameters defined as out or inout!

Issues of the design

With the definition given so far there is an important caveat: In a real-world application it would not be smart to execute the setCounter method without checking the one and only parameter. Since setCounter is a remote call and thus potentially quite slow and the test is very simple and straightforward, it would be good design to do the testing on the client side and only submit parameters that won't fail for obvious reasons – this practically means we would rather use setCounterFreely!

On the other hand, in conventional, linear programs the principle of encapsulation suggests to let the recipients handle checks and verifications. Thus, in a distributed application it makes sense to violate this principle as it often comes with a substantial performance penalty!

This is an example of a “leaky abstraction” – although syntactically we can use the remote object like any local object (in which case it wouldn't matter where we do the check) it would really be smarter to do such tests locally. In fact, one could even perform such parameter checks more than once: in the client (prior to submitting the request) and in the server (to account for those cases where the check on the client side has been omitted). Keep in mind that the performance overhead is negligible compared to the cost of a remote code execution!

In good OO design the mechanism of exceptions makes sense if the remote object signals an error that the local client could not have detected (e.g., in case of an ATM machine it might happen the user cannot withdraw money for some reason).

While the syntax looks quite familiar to object definitions in the style of C++ it is important to keep in mind that the IDL is rather the definition of an interface, not of the object itself! Thus, the CORBA middleware does not do anything to implement attributes; all that happens is that the accessor functions will be provided and it is up to the developer of the service to provide an object with the appropriate instance variable. In this sense, attributes are merely syntactic sugar for the accessor methods.

I shall also point out that the IDL does not allow private or protected members — although it can make sense to have such members in an object oriented system, CORBA is a communication-oriented middleware. So why would you need to define (and possibly communicate) member functions that the recipient cannot talk to?

For further details of the IDL I refer to overviews like the Orbix documentation.

Implementation of the service

The implementation of the objects is quite straightforward and has been provided in the file protocol_impl.cc with header protocol_impl.h. Note that the accessor methods to the counter attribute have been defined by the mapping of the IDL to the C++ language.

The object is provided by the server server.cc code which is almost identical to the corresponding server in the previous code. The only difference is that the object is used locally in the server to reset the counter after instantiation, but prior to handing the control over to the ORB:

30   // Reset the counter
31   proto_obj->resetCounter();

Implementation of the client

The client is implemented in client.cc. As before, there are a few lines of initialization code which are not important for now. Afterward, the object can be used like any local object. The client can be called with zero, one or two command line options. If invoked with no command line parameters, it will simply reset the counter to its starting value, see above. If called with one parameter, it will send the message to the server which will display it along with the current date and time and the current counter value. The counter will then be increased by one.

If the client is called with two parameters, the first parameter will be considered the message – which is displayed as usual – but the second parameter will reset the counter to a new value before the message is logged. The counter value is always supposed to be positive; if it isn't, an exception is raised in the client which will then resubmit the negative of the supplied counter value. Thus, this client implements a basic form of fault-tolerant computing – it won't fail even if the object has been called improperly!

Please remember what I said about this type of design above since this is an example of a design that should be handled differently in distributed and local object designs!

Note again that syntactically all operations of the client proceed just as if it was a regular object defined locally. The complexity of remote invocation together with possible type mismatches is handled entirely by CORBA and we do not need to worry about it at all!

Running the system

The system can be compiled by the accompanying Makefile. To test it, simply start the server in one window and then launch the client from different places with different command line options and see how the server properly handles all messages and events from all clients.

Using CORBA example code 2

Like in the previous case the client can be run as often as needed from any other window as long as the corresponding file ior.dat is in the current directory (we will overcome this limitation later in part four of this tutorial).

Again, congratulations for making it so far! You have already experienced the power and convenience of CORBA programming and seen some strengths that demonstrate the value of this middleware for enterprise-wide distributed applications!

Now you are ready to continue reading part three of the examples.