Features


Encapsulating CORBA Components with the Adapter and Bridge Patterns

Patrick May

CORBA hides the details of invoking objects on remote machines. With a little more refactoring, we can hide the details of CORBA as well.


Introduction

CORBA provides an industry standard mechanism for developing and deploying distributed objects. These objects can be implemented in a variety of programming languages and distributed over heterogeneous hardware. Well-designed CORBA systems are flexible and highly scalable. The benefits of CORBA, however, come with a high cost related to the complexity of the implementation and the difficulty of integrating with non-CORBA code.

When integrating CORBA services with C++ applications, the complexity arises from three main sources:

In this article I demonstrate these problems, and a solution, using the example of a very simple message logging service.

The Complexity of CORBA with C++

A simple distributed message logging service will demonstrate the complexity of using CORBA with C++. The logging service is a program that runs on a server and is made accessible to any number of client programs via CORBA’s distributed computing mechanism. CORBA makes it possible for a client to call the service whether that client is colocated [1] with the service, in a different process space, or even on a different machine. Clients can call the service to log events that have occurred and to provide other information, such as the name of the function where the event occurred and the severity level of the event. To log an event, a client calls one of the functions in the logging service interface. The logging service interface is a CORBA interface; this makes it possible for clients to call the service without knowing where the service is located.

Logging Service Interface

CORBA interfaces are described using IDL (Interface Definition Language). The logging service is defined by a single IDL module containing a single interface named Logger (Listing 1).

As can be seen from the IDL definition, the Logger interface consists of five methods:

The Logging Service Implementation

CORBA services are implemented by binding an implementation class written in a language such as C++ to an IDL-specified interface. This is done by compiling the IDL with an IDL-to-C++ compiler. The compiler creates what are known as skeleton files for the server side of the application. Skeleton files are C++ source files that represent the IDL-specified interface in terms of C++. These skeleton files are then compiled with C++ implementation files (using a C++ compiler) to build the service.

On the client side, the IDL-to-C++ compiler creates stub files. Stub files are C++ source files that present the service interface to client programs. They provide everything necessary for the client to call the service. These stub files are compiled along with the client source code to create the client programs.

IDL-to-C++ compilers are typically provided with (and are specific to) a particular CORBA ORB, or Object Request Broker. The ORB is a special module that handles all the communication between CORBA clients and services. An implementation of the ORB resides on both the client and server machines. To create the example implementation for this article, I used TIBCO’s TIB/ObjectBus [2] ORB, but the code should work with any OMG-compliant ORB.

The Logger interface is implemented by a C++ class with the same name (Listing 2). The CORBAImplementation [3] parameterized base class encapsulates much of the boilerplate code required by CORBA services. The Logger class includes a run method in addition to the methods specified in the IDL declaration. This method is called to start the service.

The Logging Service Client

CORBA implementations provide a program called the Naming Service, which enables client programs to locate CORBA services by name. Once the Naming Service has been started, running a wrapper program (Listing 3) that performs the necessary ORB initialization and calls Logger’s run method will enable the Logger service to be used by CORBA clients.

As the client code (Listings 4 and 5) demonstrates, the use of CORBA increases the complexity of applications and decreases understandability and maintainability:

Adding an Adapter

The Adapter pattern (as described in Design Patterns [4]) can eliminate much of CORBA’s intrusiveness by encapsulating the CORBA-related complexity and presenting a "standard" C++ interface to the client application. This use of the Adapter pattern could also be considered to be an instance of the Proxy [5] pattern.

Adding an adapter is straightforward. The Logger class is renamed to LoggerImpl to better reflect its nature and a new Logger class (Listing 6) is created to implement the Adapter pattern. Figure 1 shows the relationship between the Logger and LoggerImpl classes.

Instances of the Logger class acquire a reference to the LoggerImpl during construction [6]. When a client calls a method of the Logger class, the Logger class forwards the call to the appropriate method of the LoggerImpl by reference. The Logger also translates CORBA exceptions to C++ exceptions; any exceptions that must be propagated to the client are raised using standard C++ exception semantics. The interface of the Logger class is indistinguishable from classes that are not dependent on CORBA; no CORBA-specific data types, exception handling details, or restrictions are exposed to clients of the Logger class.

Listing 7 shows that using the adapter is much easier than using the CORBA implementation directly. The data type issues, incompatible exception handling mechanisms, and lack of support for overloaded method names in CORBA have all been resolved by adding a level of indirection, thereby decoupling the client from the intricacies of CORBA and making the resulting system more easily maintainable.

Location Independence

The addition of a simple adapter increased the usability of the Logger service significantly. A few more small changes can provide even more flexibility to clients of the service.

During development of a CORBA system it is often more productive to implement and test the functionality of a service independently of the distribution mechanism. In addition, decisions regarding which services should be distributed and which should be colocated typically cannot be finalized until late in the development process. The distribution choices can even be revisited after a CORBA system goes into production, as usage patterns change or hardware and network enhancements suggest more optimal solutions.

Location independence is therefore a valuable feature of a distributable class. Ideally, clients of the class should not be aware that a class is distributed; instances of local and distributed classes should be treated identically. It should be possible to change the distribution mechanism of a class without impacting the client code.

This decoupling of interface and implementation can be accomplished using a variant of the Bridge [7] pattern (illustrated in Figure 2).

As described in Design Patterns, the Bridge allows different implementations to be provided for a single abstraction. The appropriate concrete implementation is selected at link time or run time and is called by the Abstraction class to provide the behavior of each method in Abstraction’s interface.

Implementation of the Location Bridge

The canonical implementation of the Bridge pattern involves two separate inheritance hierarchies with the interface class maintaining a reference to a concrete implementation class. This dual hierarchy approach is unnecessary to achieve the goal of location independence; instead of the full inheritance hierarchy assumed in Design Patterns, I use just two concrete implementation classes (see Figure 3). This simplification allows for the Abstraction and Implementor abstract base classes to be merged into a common abstract base class that defines the interface used by clients of the service. This common base class is named Logger [8].

To implement the location bridge, the Logger class from the adapter-only example is renamed to LoggerCORBAAdapter (Listing 8) and modified to inherit from the Logger interface.

LoggerImpl (Listing 9) is a C++ implementation of the logging functionality.

The LoggerImpl class from the adapter-only example is renamed to LoggerCORBAImpl and is modified to use LoggerImpl for its implementation (Listing 10). In addition to eliminating duplicate code, this design cleanly decouples the distribution mechanism provided by CORBA from the service behavior provided by LoggerImpl.

The correct subtype of Logger can be selected in any of several ways at either link time or run time. Some possibilities include initialization in main, the use of the Factory [9] pattern, and tricks with static initialization. The important issue is that clients of the service are insulated from the particular subtype choice and are dependent only on the Logger class.

In this example, the Logger base class requires its subtypes to implement a clone method and to register with the base class when the first instance is constructed. The Logger class can then be used without the need for a factory.

Benefits of the Location Bridge

This combination of the Bridge and Adapter patterns provides location independence and decouples the implementation of the service from the details of the distribution mechanism. Distribution alternatives can be selected at link time by specifying different libraries or even at run time based on command-line parameters or environment variables.

From the perspective of the production users of a system, the primary benefit of using this combination of patterns is flexibility. Repartitioning behavior between multiple processes is quite simple. From the perspective of the original developers and those responsible for maintaining the system, the primary benefit is that the implementation can be easily tested independently of the distribution mechanism.

Summary

It is worth noting that several existing ORBs do support colocation of CORBA services within the client process. The solution presented here has several advantages over such mechanisms:

The use of the location bridge mechanism eliminates many of the common problems encountered when using CORBA in a C++ environment and provides some important benefits. The number of classes that must be implemented and maintained is higher than simply using "raw" CORBA, but the overall complexity of the service and the client applications is much lower.

Notes and References

[1] A colocated service is one that runs in the same process space as its clients. Naturally, a service may be colocated with some clients and distributed with respect to others.

[2] http://www.tibco.com/products/messaging/objectbus.

[3] A detailed discussion of this class is outside the scope of this article.

[4] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns, Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995), pp. 139-150.

[5] Ibid., pp 207-217.

[6] The reference to the LoggerImpl does not, of course, have to be acquired in the constructor of Logger. It could, for example, be re-acquired for each method invocation.

[7] Design Patterns, pp. 151-161.

[8] Ibid., page 153.

[9] Ibid., pp 107-116.

Patrick May is a consultant specializing in the design and development of large scale distributed object-oriented systems using C++, CORBA, relational and object databases, and whatever other tools are appropriate. He holds a degree from the Massachusetts Institute of Technology and is a U.S. citizen currently working in Luxembourg. His primary email address is pjm@spe.com.