2.2 Software components
2.2.1 Software components specification
Components and interfaces
Figure 2.1 depicts the basic concepts concerning components specification using a sim- plistic UML metamodel, adapted from [Lüders 02].
Figure 2.1: Basic component specification concepts
A component exposes its functionalities by providing one or more access points. An access point is specified as an interface. A component may provide more than one interface, each interface corresponding to a different access point. An interface is specified as a collection of operations. It does not provide the implementation of any of those operations. Depending on the interface specification technique, the interface may include descriptions of the semantics of the operations it provides with differ- ent degrees of formality. The separation between interface and internal implementa- tion allows the implementation to change while maintaining the interface unchanged. It follows that the implementation of components may evolve without breaking the compatibility of software using those components, as long as the interfaces and their behavior, as perceived by the component user, are kept unchanged with respect to an interaction model. A common example is to improve the efficiency of the implemen- tation of the component, without breaking its interfaces. As long as that improvement has no negative effect on the interaction model between the component and the com- ponent clients, and the component’s functionality remains unchanged, the component can be replaced by the new version.
A component may externalize its set of provided functionalities through its set of
provided interfaces.
In order to operate correctly, a component may require the existence of a set of inter- faces to be available in its surrounding environment. Such interfaces can be provided by other components. These interfaces are often referred to as required interfaces.
An operation is specified along with its set of typed parameters. In the metamodel represented in figure 2.1 parameters can be specialized into input or output parame- ters. In this metamodel, we consider the return value of a function as an output pa- rameter.
Contracts
An interface specification can be viewed as a contract between the provider of the in- terface and the interface’s client [Szyperski 02]. A contract establishes the obligations and benefits of each of the contract’s partners. An interface defined in a programming language such as Java [Gosling 96] can be regarded as a contract between classes im- plementing that interface and classes using the interface: a class that implements the interface is obliged to fulfill the interface specification while the interface client pro- vides arguments of appropriate types [Plösh 04].
Depending on the expressiveness of the component’s interface description lan- guage, the coverage level of the contract may vary significantly. Beugnard et al. iden- tify 4 levels of contract support, ranging from non-negotiable contracts to dynamically negotiable contracts [Beugnard 99]:
• Syntactic level contracts, such as the one used in typed programming languages as well as on interface description languages.
• Behavioral level contracts, where invariants, pre and post conditions can be de- fined and checked.
• Synchronization level contracts, concerning issues related to distribution and concurrency issues.
• Quality of service level contracts, address other non-functional properties of the components, such as performance or reliability.
Meyer [Meyer 00] uses a different taxonomy of four levels, maintaining the first two levels, suppressing Beugnard’s third level(synchronization) and breaking down Beug- nard’s fourth level into two: performance contracts and quality of service contracts. Although Meyer’s taxonomy also has 4 levels, we consider performance contracts to be part of the quality of service contracts. We will follow Beugnard’s taxonomy to guide our discussion on contracts, as it is, in our opinion, more relevant than Meyer’s, in the sense that it explicitly considers the synchronization issues that emerge when distribution and concurrency are considered.
Syntactic level contracts
The basic level of contract support is the type checking mechanism of the programming languages used in the development of the components and component-based applica- tions. On a single programming language environment, this is achieved through the
language’s type-checking mechanism. With multiple programming languages, a com- mon interface description language has to be used, so that pieces of software devel- oped in different languages can interact.
The coverage of the syntactic aspects of a contract is far from ideal. Developers cannot safely reuse components without understanding their semantics. This problem, common to other software development approaches, is crucial in CBD, as components are frequently developed by a third party and reused as black boxes. Relying on the access to their source code to understand the details of a component is usually not an option, either because the component is reused as a black box, or because the effort required for understanding the component’s semantics would be prohibitive, when the source code is available. Last, but not the least, even if the code is available and the effort to understand it in detail can be spent, the principles of encapsulation and information hiding advise practitioners against doing so. The usage of components should be dependent on their semantics, but not on a particular implementation of that semantics.
Behavioral contracts
The syntactic level of contracts does not define precisely the effect of executing oper- ations. Behavioral contracts are aimed at solving this shortcoming of syntactic con- tracts. A common approach to provide support to semantics in contracts is the usage of design by contract (DbC) [Meyer 92a]. DbC relies on three basic mechanisms: pre-
conditions, post-conditions, and invariants. Pre and post-conditions are assertions that must hold before and after the execution of an operation, respectively. As such, they are defined at the operation level, in specification languages that support DbC. An
invariantis an assertion that is kept true throughout the life cycle of the constrained model element. In the context of CBD, we can define invariants to constrain the valid states of components. Figure 2.2, adapted from [Lüders 02], adds the semantics to the component interfaces described in figure 2.1.
While some programming languages have built-in support to DbC (the most no- ticeable example being Eiffel [Meyer 92b]), others rely on language extensions [Ci- calese 99], pre-processing-based approaches [Bartezko 01,Kramer 98], additional class libraries [Guerreiro 01], or on the usage of reflection mechanisms [Duncan 98] for DbC support. At a higher abstraction level, DbC is supported in modeling languages such as the UML 2.* [OMG 07, OMG 06b]. The UML 2.0 standard includes the Object Con- straint Language 2.0 (OCL) [OMG 03b], a typed, side-effect free, specification language that allows, among other things, to express invariants on a UML model, as well as de- scribing pre and post-conditions in operations. As such, OCL provides UML with sup- port to DbC. Formal specification languages, such as the Vienna Development Method (VDM) [Jones 90, Fitzgerald 05] also support the specification of invariants, pre and post conditions.
Figure 2.2: Adding semantics to component interfaces
Synchronization level contracts
Behavioral contracts assume that the operations are executed as transactions, which may not necessarily be the case. The third level of contracts concerns the specification of synchronization and concurrency. The aim of these contracts is to describe the de- pendencies among services provided by a component, such as sequence, parallelism, or shuffle [Beugnard 99]. In other words, they define interaction protocols among co- operating components.
As we increase the sophistication of contracts, we can observe that the direct sup- port for specification of behavior at the interface level, rather than at the implemen- tation level becomes increasingly scarce on mainstream approaches to software devel- opment. In modern mainstream programming languages such as Java, or C], there is some basic support for synchronization (e.g. through the synchronized keyword, used to avoid thread interference). Of course, more sophisticated synchronization policies can also be implemented, but if these concerns are expressed as part of the implemen- tation, rather than directly on interfaces specification, they may become obscure to component users, particularly if the component is being reused as a black box.
There are several proposals for defining synchronization protocols. They are mostly based on formal approaches, such as π-calculus [Milner 92]. By using standard calculi, those approaches benefit from the support of the corresponding model checkers to for- mally derive properties such as liveness, or safety. Some of these approaches use the concept of roles (roles hide the component details which are irrelevant to the particu- lar interaction) to define a modular specification of the observable behavior of compo- nents, in the context of their interaction [Canal 03, Li 05]. This corresponds to defining the protocol governing the communication among the components playing those roles, while reducing the complexity that would result from considering the whole compo- nents. These protocol specifications are added to the component interfaces definitions.
The protocols are specified with an extension of polyadic π-calculus [Milner 93]. A less formal alternative is to sacrifice the power of process calculus in exchange for notations such as UML’s sequence diagrams, which are easier to grasp by common practitioners, but are often used to denote a simplified overview of the behavior, or common use cases, rather than complete interaction protocols between components.
Quality of service (QoS) contracts
The fourth level of contracts concerns non-functional properties (other than distribu- tion and concurrency properties). Again, this is far from being a solved problem by the research community. For instance, consider the specification of the efficiency of a software component, when performing a given task. Such efficiency may depend, to a large extent, on the environment under which the component is expected to operate. Although benchmarking information may be obtained, this may not be sufficient for component clients.
Consider the following example: a client component with strict real-time con- straints requires the services of an off-the-shelf server component for a given task to be executed in a time period smaller than a fixed threshold. Now, suppose the producer of the server component deploys an upgrade of its component that adds new services at the expense of a small efficiency loss. The client component may cease to work as ex- pected due to this server component upgrade, if it can no longer use the same service as before within its required time frame. Note that as client and server components may be produced by different organizations, the server component producer may not be aware of that specific client’s time constraints, when upgrading its component. On the other hand, the contract information available to the client may not be expressive enough to document this performance loss on the server side in a convenient way. In a distributed environment, where factors such as network overload can interfere with real-time constraints, the problem becomes even more complex.
There are some examples of non-functional properties specification at the compo- nent interface level. OMG has two standard profiles to the UML language aimed at ad- dressing real-time constraints [OMG 05a] and Quality of Service properties [OMG 06a]. These profiles extend the basic UML metamodel with meta-classes that allow repre- senting the non-functional properties. The Software Engineering Institute has a re- search stream called “Predictable Assembly from Certifiable Components” that adds an analytic interface to the constructive interface of software components. The term con- structive interface refers to our traditional notion of interface (a syntactic interface with an API description). The rationale is that these analytic interfaces provide insight on the inner workings of the components, namely on their non-functional properties. The analytic interface concept is used by Grassi et al. to extend an architecture description language (xADL [Dashofy 01]) to support those interfaces [Grassi 05].
tracts aware of non-functional properties and the body of work of compositional rea- soning. Compositional reasoning is mostly dominated by work on formal systems, and is based on a divide and conquer approach to system properties prediction: in or- der to obtain properties about the whole (in the scope of this dissertation, component assemblies), we start by obtaining properties on the parts (software components) and then compose those parts properties to obtain the properties of the whole. A common difficulty with formal approaches to compositional reasoning is their complexity and limited scalability, particularly because the most powerful techniques often require the intervention of an expert user [Berezin 98]. When scale renders current formal ap- proaches unfeasible, an alternative is to use an empirical approach, where component properties are observed, rather than asserted, or proved [Moreno 05].