Connectors: a Key Feature for Building Distributed

0 downloads 0 Views 83KB Size Report
are facing a real problem when considering heterogeneous, multivendor, distributed ... This is particularly unfriendly for code reuse since objects are tied.
Connectors: a Key Feature for Building Distributed Component-Based Architectures Vladimir Marangozov, Luc Bellissard, Jean-Yves Vion-Dury, Michel Riveill SIRAC project, INRIA Rhône-Alpes, 655, Avenue de l’Europe, F-38330 Montbonnot, France e-mail: [email protected]

Abstract Building distributed applications and frameworks that integrate legacy software components is more than ever a challenging direction in distributed software engineering. However, interconnecting distributed components and making them collaborate in a flexible and configurable way is a complicated and error prone task: actually, underlying communication requirements are often hidden within components implementation. This paper claims that the use of connectors is a way of decoupling communication and interconnection dependencies from the core implementation, thus leading to higher flexibility and increased reusability when building distributed software architectures. Connectors are a step towards both the production of a clear application structure in terms of interacting pieces of software, and the customized configuration of distributed processing according to user-defined specifications for component interoperability.

1. Introduction 1.1 Objects versus Components Object-Oriented design is now broadly accepted as being a promising technology which favors code reuse and contributes to productivity improvements, higher profitability, reduced costs and return on technology investments. However, practical experience shows that system integrators, service providers and developers are facing a real problem when considering heterogeneous, multivendor, distributed environments that encompass existing software products. Indeed, important problems still remain which are especially amplified in distributed processing. They mainly address the integration of heterogeneous pieces of software (libraries, objects, modules) originally designed in very different application contexts, as well as the reuse of contextdependent software entities at various grain-levels. Figure 1.a shows how are established the relationships among software units by the sole use of the ObjectOriented approach. Object dependencies are hidden within objects implementations and object interactions « cross » the public/private barrier. This is particularly unfriendly for code reuse since objects are tied implicitely, there is practically no control over their relationships, and thus they cannot always be considered as independent software entities. An undesired consequence in distributed computing is the fact that often the code related to communications is merged with objects core implementation, subsequently limiting their integration in another distributed environment.

public world

Titre: m2 m3 m1

m4

m6 m5

Créé par:

m7

(interfaces)

r1

p2

m8

p4 r2

p1

p3

private world (implementation)

Date de création: (a)

Objects

(b)

Components

r3 r4 M k : methods P i : provided services R j : required services : connector

Figure 1: Objects versus Components Another approach consists in defining components as existing or custom-made software pieces that provide an interface exhibiting all component dependencies. Not only provided services are shown (as in most OO languages), but also required services in order to determine at the interface level the external conditions necessary for enabling the functioning of the component. Furthermore, required services are linked with provided ones through connectors (figure 1.b). Some of the advantages deriving from this approach among others are higher degree of reusability, increased flexibility and configurability of (distributed) software architectures, and component dependency externalization.

1.2 What are connectors? It is a common assertion that within a software architecture, connectors are the complementary basic structural elements allowing components to be associated or « packaged » [Sha95a] in order to collaborate. Informal descriptions of connectors in the relevant litterature [Mon96, Gar93, Sha95b, Kaz96, Hyl96] converge to the idea that connectors are the « glue » between interacting components, but there is little consensus on terminology because of immaturity in the field of software engineering [Cle96]. Here we focus on connectors from the operational viewpoint, relevant to the instantiation of a given software architecture. It affects the implementation of a compliant software system, together with its execution and evolution over time. In this case, components correspond to the set of modules or libraries that constitute the computational units of the system, and connectors are the software parts which allow components to function together, as specified in the architecture. By preserving a certain level of genericity, the following definition characterizes connectors fairly enough: Connectors are functional bindings between components enabling their controlled interoperability. This definition emphasizes the following important points. First, connectors are functional bindings, i.e. they correspond to real, operational software units implementing the links between components, which may or may not be activated at run-time. Second, connectors make possible the interoperability between components. This means that connectors « know » how to handle the interaction of potentially incompatible components and how to adapt them to each other. Furthermore, the interoperability process is controlled. Component interactions or communications cannot be anarchical. They are « typed » and comply with pre-defined rules, the compliance being controlled by connectors.

2. Connectors for Flexible Architectures In the work achieved so far to support distributed component-based architectures, we developed a prototype system allowing to assist the software designer in building distributed applications. It consists of an architectural description and configuration language (named OCL) used for describing applications in terms of components and connectors, and a run-time environment supporting the execution of automatically generated compliant implementations [Bel96]. This section discusses the set of properties we identified as being relevant to connectors and the design principles that were adopted for implementing them within the run-time support.

2.1 Connector roles Connectors as structural links Within an overall software architecture, connectors express the logical communication channels established between component services, the latter being declared at the interface level as provided or required. The communication channels shape the set of data and control flows supposed to transit from required services to

2

provided ones, thus forming an oriented, connection graph. Several (logical) communication channels can be grouped within one connector, resulting in different service interconnection schemes: 1-1, 1-N, N-1, and N-M. For example, in OCL, a 3-2 interconnection is expressed by « R1, R3, R4 => P2, P3 using C », where Ri and Pj are respectively required and provided services, and C is the connector type used for their binding. In the case where multiple required or provided services are embedded within the same connector (figure 2), routing policies and reply handlers have to be provided for resolving the ambiguities that may arise at run-time when addressing several destinations or receiving multiple replies on a single request. Connectors as component adapters Components heterogeneity is unavoidable [Sha95a]. Therefore connectors must be able to accomodate incompatible services according to predefined rules. It is worth noting that connectors allow external compatibility management which differs from adapting the components themselves to a standard programming interface or to a common data representation. One of the most common difficulties in software engineering towards reuse and flexibility is the problem of signature matching [Zar95, Zar96]. In that sense, connectors represent an elegant solution by allowing the external specification of compatibility rules. They usually consist in series of data and type transformations with an arbitrary level of semantics modification defined by the software architect or the system developer. An example in OCL:

R(in integer i, out boolean b); // P(in boolean b, out T_String s); // A.R(i, b1) => B.P(b2, s) do { // b2 = True // b1 = eval( if len(s): True else: False ) } using C //

required service of component A provided service of component B signature matching handler explicit transformations

connector type

Connectors as communication controllers Connectors provide « typed » communications in the sense that every component interaction complies with: (1) a communication model (2) an execution model Communication models are of two basic types: (1) procedure call model (usually synchronous), which is easily extended to distributed environments by the means of RPCs and (2) messaging model (usually asynchronous) insuring once-delivery of messages, extended to distributed environments by implementing remote message queues. The above communication models can be mixed, resulting for example in the conversational model, where one part initiates a conversation with another, and then they exchange messages synchronizing as necessary, until the requests are satisfied, at which time the conversation is terminated. Each component is responsible for maintaining the state of the conversation and abiding the rules of the conversation protocol defined in the connector specification. Connectors as transport agents Connectors have the implicit role of insuring the information forward between components. This is done in a transparent way, even if intermediate data transformations or additional control operations are necessary. Moreover, communication channels rely on top of physical system resources enabling their implementation. Connectors are responsible for mapping these channels to the set of existing system resources, as well as checking and controlling their availability. Connectors are generally « aware » of the necessity in additional processing required by the transport layer and they use on a need-basis the set of available transport protocols, such as TCP, UDP, HTTP.

3

Titre: Créé par: Date de création: request/reply adapters

reception synchr. and time ordering

multiple reply handler

delivery synchr. and time ordering

request/reply adapters

request routing policy

user defined communication and/or transport handlers

__call__

R1

__call__ P1

__call__

R2

__call__ __call__ R3

Required Services

CONNECTOR

P2 Provided Services

RUN-TIME SUPPORT SERVICES: Distribution, Objects, Communication, Transport, Local OS/IPC

Figure 2: Connector Architecture Overview

2.2 Design principles Distribution transparency In order to reduce the effort of programming distributed systems (which is a complex and an error prone task), several transparencies are normally introduced, including location transparency, namespace transparency, administrative transparency, secured access transparency, and communications transparency. Transparencies hide the complexities related to distribution and help the software architect to perceive an unique image of the system. The "single system image" principle implies several connector implementations for each type of connector, because there are no assumptions a priori of the component distribution over the set of machines connected to the network. Therefore, the following three possible cases have to be considered: 1. Local Communication (the components belong to the same context in the sense of a Unix process) 2. Inter-Process Communication within the same node (does not involve network communication) 3. Inter-Process Communication between remote nodes (imply network communication) By supporting several implementations of the same connector, significant performance gain can be obtained, together with increased flexibility and adaptability to different architectures and/or customized environments. In our prototype, connectors offer a single, simplified programming API to all components, consisting in only one primitive « __call__(*user_data) » which executes synchronously for the procedure call model, and asynchronously for the messaging model. Modularity Connector implementations may vary significantly depending on functionality of the underlying system services. Therefore, there must be a clear separation of concerns when considering distributed implementations, resulting in discrimination between communication services, distribution services, object services, transport services, and local operating system inter-process communication services. Many of the above (distributed) sub-systems comply with existing standards and can be used « as is » for building connector implementations on top of them. A connector implementation in this case consists in specializing some part of its generic architecture according to the APIs offered by those sub-systems. Modular connector design enables, on one hand, to apply partial instantiation of the connector architecture for the parts that are not supported on a given system, and on the other hand, to selectively « plug-in » user-defined handlers or new implementations of the parts that constitute the « connector body ». Neverthless, a full proprietary implementation is still possible, such as direct sockets programming, as long as it complies with the semantics of the « __call__ » primitive and insures the desired functionality.

4

3. Connectors and Distributed Components: Experience Report 3.1 The OLAN Environment The experiments undertaken have led us to the definition of a complete environment for the construction of distributed applications. The building process can be separated into three tasks in the following order: (1) construction of components and identification of existing software modules to be reused, (2) architectural description of the application through composition, (3) verification of the validity of the composition and generation of compliant implementation structures. Furthermore, the application is deployed or distributed over a set of nodes according to the configuration requirements, then it is executed. The OLAN environment provides a set of tools for the description, the configuration and the deployment of a distributed application. At the design stage, applications are described with the help of a configuration language, namely OCL (Olan Configuration Language), which allows to express the compositional integration of various components into a global architecture, as well as different criteria relevent to distribution. The OCL compiler is in charge of both checking the validity of the configuration description and generating its executable image. Subsequently, the OLAN run-time support allows the installation and the execution of the application on different networked computer nodes.

3.2 Components and Interconnections Components are the units of software encapsulation and distribution. They exhibit an interface describing the services provided to other components, along with the external services required at run-time. A component implementation fulfills the requirements of the interface by mapping respectively the encapsulated pieces of software with their interface description. Two kinds of implementations are available: a primitive implementation directly encapsulates software modules or classes written in different programming languages (currently C, C++ and Python), while composite implementations are made of interconnected components, either primitive or composite. The following example illustrates a primitive component definition in OCL: class ‘C++’ Account { name: ‘account.cpp’; path: ‘/project/example/’; compiler: ‘g++’; ... };

primitive implementation AccountImpl : AccountItf uses Account { AccountNumber => Account.acct_Id; Deposit => Account.Add(in float amount); Withdrawal => Account.Subtract(in float amount); }; management AccountAdm : AccountImpl { Node.os == ‘AIX4’ };

interface AccountItf { attribute long AccountNumber; provide Deposit(in float amount); require Withdrawal(in float amount); };

component Account { implementation AccountImpl; management AccountAdm; };

Connectors are used in OCL to express interconnections between components, thus enabling to create composite components from primitive ones. In the following example, a « transfer agent » component is connected with an « account » through a « money transfer » connector: implementation BankOffice : BankOfficeItf uses AccountItf, TranferAgentItf { theAccount = instance AccountItf; theManager = instance TransferAgentItf; ... theManager.Transfer(amount) => theAccount.Deposit(amount), theAccount.Withdrawal(amount) using moneyTransfer; ... };

The « moneyTransfer » connector is available from a user-defined connector library. It complies with the procedure call model and uses a routing policy for provided services discrimination based on the arithmetic sign of the input parameter (in our case « amount »). Here is a formalized description: connector moneyTransfer uses Proc_Call { inputs IN[1,1]; outputs OUT[2,2]; ... routing { on IN[1](x) select { if x > 0: OUT[1](x) else: OUT[2](x) } } };

// moneyTransfer // input services // output services

=> proc_call model => [nb_min, nb_max] = [1,1] => [nb_min, nb_max] = [2,2]

// routing handler

=> output selection

5

3.3 Connectors: configuration and run-time support The runtime support architecture relies on several abstract machines: the component machine, in charge of component structures, the connector machine, in charge of connectors and their instantiation, and finally the configuration machine, in charge of the deployment and the installation of the application. Here we focus on the functionalities of the connector machine which is activated once the set of components have been defined and configured in a distributed environment. The main role of the connector machine is to implement the OCL statements related to service interconnections. This is achieved through a basic configuration primitive of the form: bind(, , )

where each service has a unique identifier made of its name and the identifier of the component it belongs to. Furthermore, component identifiers are unique within the distributed environment and are constructed by the configuration and the component machines. The connector machine implements the « bind » function by creating a connector instance (figure 3) of the given connector_type, which consists in the following operations: 1. 2.

3.

Create service adapters for every component involved in the bind primitive. Create the « connector body », which may be distributed over several nodes, and specialize it by plugging all handlers defined in the connector specification. This operation is subject to optimisations aiming to minimize remote communications and replicate, if possible, some parts of the connector body in order to achieve better performance at run-time. Update service-connector and connector-service references in service adapters created at step 1. node 3 Adapter

Titre:

_call_

Component Interface

node 1

CrééAdapter par: Routing Handler

Component Interface

_call_

Adapter _call_

Component Interface

Date de création: Adapter _call_

node 2

Component Interface

Figure 3: Distributed connector instantiation

3.4 Experiments and results The first step towards validation of these proposals is the experimentation of realistic application building using the OLAN framework. We have considered a CSCW application aiming to provide support for synchronous teleconferencing between distributed workers. Basically, each participant in a teleconference session has an editor window displaying in real-time and in a WYSIWIG way the operations done on a shared document. When entering the session, a role is affected to each participant and a floor passing policy rules the ability to write on the document. Additional information on this application may be found in [Bel95]. We have extended our experiments in this direction by designing a cooperative World Wide Web browser. The application is based heavily on existing modules, originally developed for a stand-alone web client [Gra96], to which we added a cooperative controller module implementing both users rights to drive or just follow the browsing session, and an IRC-like message system between the involved participants. Thus, we used connectors on one hand, for binding the web client modules with the controller, and on the other hand, for interconnecting the controllers between them and implementing the floor passing policy. A number of benefits deriving from the use of connectors towards flexibility have been validated. It was easy, for example, to switch from one floor passing policy to another by simply changing the connector type used between controllers, and notably the routing handler which is plugged for service selection. Another point is the possibility of incremental configuration and customization of the application by testing the same set of components interconnected with different quality of service requirements. For example, in the cooperative browser application, the user communication messages do not have the same importance as the dialog established between controllers when changing the floor owner. Therefore it is suitable to use UDP-based

6

connectors for user messages (faster, without delivery guarantee), but it is not for those ensuring the floor token transmission. Such incremental configuration is done without any interventions regarding component implementations, which is useful for rapid prototyping, scenario testing and better adaptability to existing distributed environments.

4. Conclusion This paper introduces connectors as functional bindings, enabling component interoperability in both local and distributed contexts. The first consequence of this approach is to give a more important role to interconnections: the code related to communications is now external to the component and becomes visible, configurable and reusable. The use of connectors is a way of decoupling communication from the core implementation, thus leading to explicit configuration of the communication protocol being used. Connectors allow to obtain different interconnection topologies between the same set of components, as well as to connect, a priori, incompatible services by using predefined marshalling rules. New connector types can be easily obtained either by providing different implementations, either by enriching existing ones with user-defined plug-in handlers. At the current stage, the OLAN framework is implemented on top of a distributed object oriented platform available on Unix environments. This imlementation strategy is expected to allow rapid application design with an extra cost due to the OLAN run-time system. Future works include studies on high-level formal connector specifications, together with exploring methodologies for building adaptive connectors for high performance applications.

References [Bel95]

[Bel96]

[Cle96] [Gar93]

[Gra96] [Kaz96]

[Mon96] [Sha95a] [Sha95b] [Sha96]

[Hyl96]

[Zar95] [Zar96]

Bellissard L., Ben Atallah S., Kerbrat A., and Riveill M. "Comonent-based Programming and Application Management with Olan", Object-Based Parallel and Distributed Computation France-Japan Workshop, OBPDC’95, Briot J.P., Geib J.M., Yonezawa A. (Eds.), LNCS 1107, Springer Verlag, 1996. Bellissard L., Ben Atallah S., Boyer F., and Riveill M. "Distributed Application Configuration", Proceedings of the 16th International Conference on Distributed Computing Systems, pp. 579-585, IEEE Computer Society, Hong-Kong, May 1996. Clements P. and Northrop L. "Software Architecture: An Executive Overview", Technical Report CMU/SEI-96-TR-003, Software Engineering Institute, Carnegie Mellon University, February 1996. Garlan D. and Shaw M. "An Introduction to Software Architecture", Advances in Software Engineering and Knowledge Engineering, Vol.1, River Edge, NJ: World Scientific Publishing Company, 1993. Grail Home Page, URL:http://monty.cnri.reston.va.us/grail/, The Corporation for National Research Initiatives, Reston VA, 1996. Kazman R., Clements P., Abowd G., Bass L. "Classifying Architectural Elements as a Foundation for Mechanism Matching", Proc ACM SIGSOFT Symposium on the Foundations of Software Engineering (submitted), 1996. Monroe R. and Garlan D. "Style-Based Reuse for Software Architectures", Proceedings of the 1996 International Conference on Software Reuse, Orlando FL, April 1996. Shaw M. "Architectural issues in software reuse: It's not just the functionality, it's the packaging", Proceedings of the IEEE Symposium on Software Reuse, April 1995. Shaw M. and Garlan D. "Formulations and Formalisms in Software Architecture", Computer Science Today, Lecture Notes in Computer Science,Vol 1000, pp. 307-323, Springer-Verlag, 1995. Shaw M. and Clements P., "A Field Guide to Boxology: Preliminary Classification of Architectural Styles for Software Systems", manuscript, Computer Science Department and Software Engineering Institute, Carnegie Mellon University, April 1996. Hylton J., Manheimer K., Drake F., Warsaw B., Masse R., van Rossum G. "Knowbot programming: System support for mobile agents", Proceedings of the Fifth International Workshop on Object Orientation in Operating Systems (IWOOOS'96), Seattle, WA, October 1996. Zaremsky A.M. and Wing J.M. "Signature Matching: a Tool for Using Software Libraries", ACM Transactions on Software Engineering and Methodology, April 1995. Zaremsky A.M. and Wing J.M. "Specification Matching of Software Components", submitted to ACM Transactions on Software Engineering and Methodology, June 1996.

7