Composing Distributed Objects in CORBA

advertisement
Composing Distributed Objects in CORBA
Jeff Magee, Andrew Tseng and Jeff Kramer
Department Of Computing
Imperial College of Science, Technology and Medicine
180 Queen’s Gate, London SW7 2BZ, United Kingdom
E-mail:{jnm,jk}@doc.ic.ac.uk
Abstract
The paper addresses the problem of structuring and managing large distributed systems
constructed from many distributed objects. Specifically, the paper proposes a component
model which can be used to compose objects into manageable entities. Components are
specified using Darwin, an architecture description language developed by the authors. A
mapping of distributed objects into Darwin components is described together with an
outline of how Darwin and its associated tools are implemented in a CORBA compliant
environment.
keywords: distributed software architecture, components, object management
1. Introduction
A recent study in software maintenance for distributed systems [12] has indicated that the move to
distribution has contributed to the simplification of primitive software components in a distributed
architecture. However, this benefit is often overwhelmed by the increased complexity of the
overall distributed system and its architecture. The architectural description language (ADL)
Darwin[7] is designed specifically to help manage this problem. Darwin is a declarative language,
intended as a general purpose notation for specifying the structure of systems composed from
diverse components using diverse interaction mechanisms. It deliberately separates the description
of structure from that of computation and interaction in order to provide a clear separation of
concerns. Darwin supports the specification of both static structures fixed during system
initialisation and dynamic structures which evolve as execution progresses [8].
In this paper, we describe the use of Darwin to structure distributed systems implemented using
CORBA[1] distributed objects. The approach is to define a a mapping from the CORBA object
model into the Darwin component model. Essentially, the concerns of structuring a large system
from a set of components are taken to be orthogonal to the concerns of programming its
functionality as a set of interacting objects. Object interaction and the associated issue of interface
compatibility are the concern of the Object Request Broker(ORB) and the Interface Definition
Language (IDL), while the concerns of organising a set of distributed objects into a manageable
1
architecture, we argue, are best dealt with using an ADL such as Darwin and its associated tools.
We take the view articulated by Nierstrasz and Tsichritzis [9] that components are complimentary
to objects and that whereas current object oriented languages emphasise programming,
components are concerned with composition.
In the following, using a simple example, we motivate the use of a component model to structure a
system of distributed objects. Section 3. then outlines how the Darwin component model and the
ability to compose components into complex systems can be superimposed on the basic CORBA
object model without requiring modifications to either the model or the associated services and
facilities. Details of a prototype implementation are presented together with some examples of the
use of Darwin to structure CORBA systems.
2. Objects & Components
To illustrate the role of objects and components in constructing distributed systems, we will use
the simple example depicted in Figure 1 of a pipeline of filter objects which is fed a stream of
integer values by a producer object at one end and connected to a consumer object at the other.
Each filter object is instantiated with its own integer value and removes multiples of that number
from the stream. For example, filter(2) removes all even numbers. Each object may potentially be
located on a different processor.
Producer
Filter
Object
Reference
Filter
Object
Interface
Consumer
Object
Implementation
Figure 1 - Filter pipeline example
The Common Object Request Broker Architecture (CORBA)[1] is primarily concerned with
facilitating transparent access to remote objects. It clearly separates the specification of the
interface of an object from the implementation of that object. Interfaces in CORBA are specified
in an Interface Definition Language (IDL). This interface specifies exactly those operations which
can be invoked by a client and it is used to generate proxy objects in an implementation language
which hides distribution from the client. CORBA specifies mappings into different
implementation languages such as C and C++. In the example, it is clear that the interface to the
filter objects and those to the consumer object should be of the same type. A suitable IDL
specification of an interface which provides a single operation for communicating an integer
value is given below:
interface IntStream {
void put(in long x);
};
2
This interface must be implemented by the filter and consumer objects. However, we are left with
the problem of how the consumer object finds out which filter object it should send values to and
how each filter object knows which is the next filter in the pipeline. To do this, we must extend the
interface of each object to include an operation which can be used to inform it of the reference of
the next object in the pipeline as shown below:
interface Producer {
void next(in IntStream f);
};
interface Filter : IntStream {
void next(in IntStream f);
};
interface Consumer : IntStream {
};
The Filter interface now extends the IntStream interface with the operation next which can be used
to send the reference of either another filter object or the reference to the consumer object since
the Consumer interface is also derived from IntStream. The remaining design problems are how
do we instantiate the structure of Figure 1 and how do give each filter object the value it is
intended to filter out of the integer stream. CORBA itself does not specify a standard way of
creating objects, however, the associated Common Object Services Specification (COSS)[2]
outlines a scheme using factory objects. The interface for a simple factory object which can create
filter objects is given below:
interface FilterFactory {
Filter createFilter(in long x);
};
where x is the value which would be passed to the constructor of the new filter object. To create
the pipeline, another “builder” object is required which constructs the system of Figure 1 by
creating objects using the factories and passing references to the newly created objects using the
their next operations.
In summary, to create the simple system of Figure 1, in addition to programming the
implementation of the Producer, Filter and Consumer objects, the programmer must provide
factory and builder objects to construct the system. The IDL specifications do not give a clear
picture of the structure of the system. They do not clearly indicate that each Filter component has
a constructor parameter. The programmer has been forced to program a next operation which has
little to do with the function of the objects. In a more realistic example, an object might require
references to many other objects and the programmer is required to ensure that these bindings are
made correctly. The structure of the system is actually embedded in the implementation of the
builder object. In a larger more realistic system, this would lead to maintenance problems. These
structuring and object interconnection problems are precisely those addressed by architectural
description languages such as Darwin[7].
3
Components
Darwin components specify not only the service interface provided by the component but also the
interfaces required from other components. Figure 2 shows the set of component types which
would be used to construct the pipeline system of Figure 1.
:Producer
output
:Filter
input output
:Consumer
input
:T
component Producer {
require output<IntStream>;
}
component Filter (long x) {
provide input <IntStream>;
require output<IntStream>;
}
component Consumer {
provide input <IntStream>;
}
component of type T
required service interface
provided service interface
Figure 2 - Darwin component types
A provided service interface refers to a service implemented within a component whereas a
required service interface refers to an interface provided outside the component. In Figure 2, the
type of each service interface whether provided or required is IntStream. IntStream is the interface
specified using IDL as before. Components thus interact using CORBA remote object invocation
protocols as before. The component specification can also include initialisation parameters which
are required to instantiate the component as in the Filter component example. In contrast to
CORBA objects, a component type in Darwin specifies the complete context for the
implementation of that component. This context describes both provided and required service
interfaces and initialisation parameters. In general, components may provide zero or more services
and require zero or more services.
Systems are specified in Darwin by describing the set of component instances and the set of
bindings between required and provided services as shown in Figure 3 for a simple filter system
consisting of a Producer, a Consumer and a Filter component. The specification declares
instances of the three component types with their actual parameters where appropriate.
Component composition is accomplished by declaring bindings between required and provided
services as in P.output -- F.input. The Darwin compiler checks that bindings are made between
requirements and provisions of compatible interface types. To do this it must have access to the
IDL interface descriptions. A Darwin specification such as that of Figure 3 is intended to be used
directly to construct the desired system at runtime. The implementation effect of a binding is to
4
place a reference to a provided service in the component requirement bound to that service. In
general, many requirements may be bound to a single provided interface in exactly the same way
that many CORBA clients may access a service provided by a server object.
:System
F(2)
P
output
C
input output
component System {
inst
P:Producer;
F:Filter(2);
C:Consumer;
bind
P.output -- F.input;
F.output -- C.input;
}
input
J
instance J
binding
Figure 3 - Simple Filter system
Composite Components
The Producer, Filter and Consumer components are examples of primitive components which
have a computational behaviour defined in a programming language such as C++. We will see in
the next section that primitive components can be implemented in a CORBA environment in a
way which is consistent with the implementation of CORBA interfaces. To manage the structural
complexity of large systems, Darwin has the capability to allow composite components to be
constructed from more primitive component types. These composite components have no
computational behaviour other than the composite behaviour of their constituent components.
The example of Figure 4, shows a composite component which encapsulates a pipeline of filter
components. The number of filter instances is determined by the parameter n substituted when an
instance of the Pipe component is elaborated at runtime. The Pipe component type is
implemented by an array of filter instances dimensioned by the array declaration. The replicator
construct forall range declares the actual instances and their bindings. Each Filter instance is
declared explicitly since they have different parameter values determined by the function fvalue.
This function is defined in the implementation language and can either compute filter values or
read them from a file. The guard construct when expression, only includes associated bindings
and instances in an elaborated system if expression evaluates to true.
The example of figure 4 locates each filter instance F[k] on a different host computer by means
of the annotation @k+1. The integer machine identifiers are mapped to real machine addresses by
the distributed component factory service. This level of indirection in mapping permits portable
specifications. In general, instances are located at the machine on which the enclosing component
is elaborated unless they are annotated.
5
The Pipe component can be substituted for the Filter component in the simple filter system of
Figure 3 to give a system with multiple filters.
:Pipe(n)
F[0]
input
input
F[n-1]
output
input
output
component Pipe (long n) {
provide input <IntStream>;
require output <IntStream>;
}
array F[n]:Filter;
forall k:0..n-1 {
inst F[k]( fvalue(k) ) @ k+1;
when k<n-1 bind
F[k].output -- F[k+1].input;
}
bind
input -- F[0].input;
F[n-1].output -- output;
Figure 4 - Composite Component Pipe
Further details of Darwin and its associated toolset may be found in [3,5,6,7,8]. We have attempted
to demonstrate in this section that while the CORBA approach deals elegantly with helping the
programmer deal with distribution by clearly separating the specification of interfaces from object
implementations and by providing transparent access to remote services, it does not support an
architectural view of distributed applications. The architecture of a CORBA distributed application
is embedded in the various factory and builder objects which construct the system. The use of an
ADL such as Darwin which describes a system as a set of interconnected components and which
thus provides an explicit structural specification of the system augments CORBA with such an
architectural view. The importance of software architecture in the design, construction and
maintenance of large complex systems has recently been realised by a number of researchers and
industrialists[4,13].
6
3. Components in CORBA
The Darwin descriptions of the previous section specify the structure of the filter application while
embedding its computational behaviour in primitive components. The behaviour is determined by
object implementations and these object implementations interact via interfaces specified in IDL
using the ORB in the normal manner. Primitive components encapsulate objects and specify their
instantiation, their required interfaces and provided interfaces. As depicted in Figure 5, a primitive
component may embed one or more objects.
non- CORBA
interaction
(a)
(b)
Figure 5 - Embedding objects in components
A primitive component may contain more than one object implementation if the objects require
to be colocated. Typically, colocation is required because the object implementations interact
directly, perhaps for efficiency reasons passing pointers to shared resources, and consequently do
not use CORBA interaction and do not have IDL interfaces specified for these interactions.
However, the usual case is that each primitive component contains a single object and thus has a
single provision as depicted in Figure 4(a).
Primitive Component Implementation
To implement components within the CORBA environment, they must be given a representation
which can be manipulated and managed by CORBA services. Consequently, each component is
implemented as an object which has an interface specified in IDL. Both the interface specification
and the object implementation (in C++) are generated by the Darwin compiler from the
component description. For example, the IDL generated from the Filter component Darwin
description of Figure 2 is given in Figure 6.
Darwin
IDL
component Filter (long x) {
provide input <IntStream>;
require output<IntStream>;
}
interface Filter : Component {
readonly attribute IntStream input;
attribute IntStream output;
void init(in long x);
};
Figure 6 - Darwin to IDL translation
7
Each provision in the Darwin specification is translated into a read only attribute of the correct
object reference type. The attribute is read only since it is set by the implementation of a
component when the component is instantiated and must not be modified from outside since it is
a reference to the application object embedded in the component. Each requirement is similarly
mapped to an attribute which is not read only since it is set externally to reflect the binding of the
component instance. Component parameters become parameters to the operation init which when
invoked creates the application object. IDL interfaces whether primitive or composite are derived
from the IDL Component interface type. This permits component objects to be treated uniformly
by the distributed Darwin elaboration algorithm which constructs the system at runtime.
Filter
IntStream
put()
input()
output()
init()
Filter_C
Filter_I
Figure 7 - Filter Component Class diagram
Figure 7 is an OMT[11] class diagram depicting the relationships between the various classes
generated by the IDL compiler and by the Darwin compiler for a primitive component. Filter and
IntStream are the classes generated by the IDL1 compiler from the component and service
interfaces respectively. Filter_C is the component implementation generated by the Darwin
compiler. It implements the init operation which creates a new instance of the filter
implementation class Filter_I. The Filter_C class maintains a reference to the Filter_I object since
this is the object reference returned by the input attribute. The Filter_I class is written by the
programmer who implements the functionality of the filter component. It differs from normal
C++ implementations only in the way external interfaces are accessed. To access the required
interface output, the reference is obtained by accessing the Filter_C object. A reference to
Filter_C is passed by init when the filter implementation object is created.
The schema depicted in Figure 7 was adopted for two reasons. Firstly, it permits the component
management object to control more than one application object and, secondly, the alternative
schema considered which used only a single object to represent a component was not strictly
CORBA compliant since it meant that a single object had two interfaces. While Orbix[10], the
CORBA implementation used in prototyping, permits an object to have more than one interface
(using TIE objects), this is not standard.
1
In the interests of simplifying the exposition, we have ommitted the details of the classes necessary for
interworking with the Basic Object Adapter (BOA) and of the CORBA::Object class from which all inetrface classes
must be derived.
8
Composite Component Implementation
Composite components are implemented in exactly the same way as primitive components in that
the Darwin compiler generates an IDL description of the component interface and a C++
implementation for that component. For example, the IDL interface generated for the Pipe
composite component is depicted in Figure 8.
Darwin
IDL
component Pipe (long n) {
provide input <IntStream>;
require output <IntStream>;
...
}
interface Pipe : Component {
readonly attribute IntStream input;
attribute IntStream output;
void init(in long n);
};
Figure 8 - Pipe composite component IDL
Composite components have no application computational behaviour, consequently, there is no
application object. The composite component implementation object is compiled directly from
the Darwin specification for that component. These objects compiled from Darwin descriptions
are builders for the application structure. An application constructed from a Darwin specification
starts by instantiating the top-level composite component. This instantiates its constituent
components until the leaf components instantiate the application computational components they
embed. When instantiation completes, the builder objects representing each composite
component cooperate to bind requirements to provisions as specified by the Darwin structural
description. Binding is accomplished by obtaining provided references from the attributes of
primitive components and setting the required interface attributes to these values. A detailed
specification for the Darwin elaboration may be found in [7]. This was first implemented in the
Regis system[6] and adapted for the current CORBA prototype. The algorithm results in
primitive components being bound directly to primitive components as depicted in Figure 9.
There is no indirection overhead and object invocations are not routed through intermediaries. A
CORBA system structured using Darwin incurs no extra overhead on distributed invocations
between objects.
C1
Elaborate
C1
S
S
C2
C2
Figure 9 - Elaborating composite components
9
In contrast to the Regis system where the composite component builder entities terminate after
instantiation is complete, in the CORBA system, we have chosen to keep builder objects so that
they may be interrogated by management tools to display and modify the structure of the
system[3].
In this section, we have outlined how Darwin components, both primitive and composite, may be
implemented in a CORBA environment as objects with interfaces specified in IDL. This means
that builder objects may communicate using the standard object invocation protocols and that
these objects representing components may be created and accessed using standard services. An
application constructed from a Darwin specification results in exactly the same configuration of
computational objects as if the builders and factories were programmed explicitly. The levels of
indirection used in the specification to structure a large system into a hierarchical composition of
components has no runtime overhead since Darwin elaboration results in direct binding between
primitive components.
4. Conclusion
In this paper we have attempted to show that there is a need for techniques and tools to help
structure systems of distributed objects, even for simple examples. Environments such as CORBA
help in dealing with distribution by clearly separating the specification of interfaces from object
implementations and by providing transparent access to remote services, but do not support an
architectural view of distributed applications. The architecture of a CORBA distributed application
is embedded in the various factory and builder objects which construct the system. Furthermore,
they lack the ability to compose objects so as to build more complex structures.
In order to overcome this problem, we advocate the use of an architectural description language
such as Darwin which views a system as a set of interconnected components, which provides for
hierarchical component composition and which provides an explicit structural specification of the
system. In additon, these structures can be parameterised and can include component instance and
binding replication, conditionals and even recursion. We have shown how Darwin can be used to
augment CORBA with an architectural view and how to map distributed objects into Darwin
components. The Darwin specification is used directly to construct the required system. The
Darwin description of a distributed application thus relieves designers and programmers of the
non-trivial task of explicitly providing code to construct and initialise the distributed application.
As we have seen from the previous section, the component, instantiation and binding abstractions
used in Darwin can be mapped to existing CORBA abstractions and services. The use of Darwin
does not impose any extra application object interaction overheads.
The ideas outlined in this paper have been prototyped using Orbix 2.0[10] for Solaris. This is a
multi-threaded CORBA implementation. We have not discussed the synchronisation issues raised
by the approach. For example, at what point during elaboration when do required interfaces
become valid for use by an application object. The prototype has adopted the approach to this
problem documented in [7] which specifies an elaboration algorithm is both concurrent and
distributed.
10
We have concentrated in this paper on describing how CORBA objects may be composed using
the Darwin component model. However, applications constructed using Darwin may freely
communicate with other distributed CORBA applications which do not use Darwin. Darwin
includes the ability to export services into a name space and import services from a name space.
This facility can use the CORBA name service to access external services and provide services
which can be accessed by others. This facility is not currently implemented in the prototype but is
planned for the near future. In addition, the current prototype uses an ad-hoc implementation for
the factory service used to create components. We plan to revise this in line with the CORBA
Common Object Services specification for life cycle management.
Finally, we think that a major contribution of this work has been to demonstrate that it is possible
to impose a component model on CORBA without requiring extensions to the CORBA IDL. We
believe that the IDL is an elegant solution to specifying interfaces. It is not appropriate to
extended it for tasks for which it was not intended - namely architecture specification.
Acknowledgements
The authors would like to acknowledge discussions with our colleagues in the Distributed
Software Engineering Section Group during the formulation of these ideas - in particular,
Naranker Dulay.
References
[1]
The Common Object Request Broker Architecture and Specification, Object Management
Group, Revision 2.0. 1995
[2]
CORBAservices: Common Object Services Specification, Object Management Group,
Revision Edition March 31, 1995.
[3]
S. Crane, N. Dulay, H. Fosså, J. Kramer, J. Magee, M. Sloman, K. Twidle, Configuration
Management for Distributed Systems, Proc. of the IFIP/IEEE International Symposium
on Integrated Network Management (ISINM 95), Santa Barbara, May 1995, Chapman and
Hall 1995.
[4]
D. Garlan, D.E. Perry, Introduction to the Special Issue on Software Architecture, IEEE
Transactions on Software Engineering, 21 (4), April 1995, pp 269-274.
[5]
Keng Ng, Jeff Kramer, Jeff Magee and Naranker Dulay, The Software Architect's Assistant
- A Visual Environment for Distributed Programming, Proceedings of Hawaii
International Conference on System Sciences (HICSS-28), January 1995.
[6]
J.Magee, N. Dulay, J. Kramer, Regis: A Constructive Development Environment for
Distributed Programs, Distributed Systems Engineering Journal, Vol. 1, No. 5., pp 304312.
[7]
J. Magee, N. Dulay, S. Eisenbach and J. Kramer, Specifying Distributed Software
Architectures, Proc. of 5th European Software Engineering Conference, ESEC ‘95,
Barcelona, September 1995.
11
[8]
J. Magee, J. Kramer, Dynamic Structure in software Architectures, accepted for Proc. 4th
ACM SIGSOFT Symposium on the Foundations of Software Engineering, San Francisco,
October 1996.
[9]
O. Nierstrasz, D Tsichritzis, Object Oriented Software Composition, Prentice-Hall: The
Object-Oriented Series, 1995.
[10]
Orbix -2, Progarmming and Reference Guides, IONA Technologies Ltd ., Release 2.0,
Novv. 1995.
[11]
J. Rumbagh, M. Blaha, W. Premerlani, F. Eddy, W. Lorenson, Object-Oriented Modelling
and Design, Prentice-Hall Internation Editions, 1991
[12]
S. Schneberger, Software Maintenance in Distributed Computer Environments: System
Complexity versus Component Simplicity, Proc. of IEEE International Conference on
Software Maintenance ICSM 95, 1995, pp304-313.
[13]
D. Soni, R. Nord, C. Hofmeister, Software Architecture in Industrial Applications, Proc.
17h IEEE International Conference on Software Engineering (ICSE17), Seattle, Italy,
1995, pp196-210..
12
Download