|
Coordination Contracts: Concepts and Methodology This document contains a first introduction to the use of Coordination Contracts and associated engineering methodology. It presents the various concepts and techniques in a simple, progressive way, that should be complemented by selected readings from our publications page. A word of warning, though: do not use in the CDE the notation found in the papers!. This is because, on the one hand, the notation itself has evolved a lot and, on the other hand, in the papers we tend to use a more abstract notation in order to simplify the presentation. Consult the Contracts Reference section for more details on how to write your Java contracts in the CDE.
1. Methodology - The 3Cs approach a. What are the 3Cs
An analogy with an orchestra is perhaps useful. The "computation layer" of the orchestra is given by the players, each of which is able to play a number of instruments. Each instrument has a certain functionality and properties (provides a certain sound) but the sound produced by the orchestra as a whole at each instant is an emergent phenomenon that results from the the instruments that are playing, the notes they are playing, and their relative positions in the orchestra. Each player follows a score that determines the order in which notes need to be played. The conductor acts as a coordination layer on top of the music instruments, ensuring that the right instruments are played at the right time at the right places. The conductor, however, does not act arbitrarily: it follows a score that is different from the one each player follows because it contains all the information that is relative to the configuration of the orchestra.
b. Why the 3Cs One of the main reasons for advocating the separation of these concerns is that it facilitates the evolution of systems. Changes that do not require different computational properties can be brought about either by reconfiguring the way components interact, or adding new connectors that regulate the way existing components operate, instead of changing the components themselves. This can be achieved by superposing, dynamically, new coordination and configuration mechanisms on the components. If the interactions were coded in the components themselves, such changes, besides requiring the components to be reprogrammed, would probably have side effects on all the other components that use their services, and so on, triggering a whole cascade of changes that is difficult to control. On the other hand, the need for an explicit configuration layer, with its own primitives and methodology, is justified by the need to control the evolution of the configuration of the system according to the business policies of the organisation or, more generally, to reflect constraints on the configurations that are admissible (configuration invariants). This layer is also responsible for the degree of self-adaptation that the system can exhibit. Reconfiguration operations can be programmed at this level that will enable the system to react to changes perceived in its environment by putting in place new components or new interconnections. In this way, the system can adapt itself to take profit of new operating conditions, or reconfigure itself to take corrective action, and so on.
c. How to make it happen For this approach to be effective, both at system design and implementation, this layering must be strict. This means that each layer makes use of the services of the layer(s) below without them being even aware of the layer above: if the components have built-in dependencies on the way they will be coordinated and configured, it will be much harder to evolve the system because it is usually very difficult to anticipate which coordination and configuration mechanisms will be needed to respond to change in the application or technological domains. Our main responsibility in proposing a new model is, therefore, to provide the means through which the proposed separation can be supported, both at the modeling and deployment phases. Coordination Contracts are the conceptual units with a precise semantics and implementation strategy that currently support the modelling and deployment of the coordination layer of the previous general architecture.
2. Coordination Contracts a. What and Why In general terms, a coordination contract is a connection that is established between a group of objects (participants). Through the contract, rules and constraints are superposed on the behaviour of the participants. From a static point of view, a contract defines what in the UML is known as an association class. However, the way interaction is established between the participants is more powerful than what can be achieved within the UML and OO languages because it relies on a mechanism of superposition that overrides direct, explicit method invocation, and replaces it with an external trigger/reaction kind of interaction. More precisely, in the context of what the CDE makes available for coordinating Java programs (see our publications for the more general coordination capabilities that can be made available), this form of interaction allows for calls made from a client object to a supplier object to be intercepted by a contract which then superposes specific reactions to be executed synchronously and atomically as a transaction that may involve actions from the other participants and local actions of the contract itself. In order to provide the required levels of pluggability and dynamic adaptability, neither the client, nor any other object in the system, will "know" what kind of coordination is being or will be superposed. To enable this, a contract micro-architecture allows coordination contracts to be superposed on given objects in a system to coordinate their behaviour without having to modify the way the objects are implemented (black box view). Using
an abstract notation, a coordination contract is defined as follows: Each
interaction under a coordination rule is of the form:
The name of the rule identifies a particular form of coordination; it identifies a point of “rendez-vous" in which the participants synchronize their local behaviour. The names themselves are used for managing the interference between different contracts that may be active in the same state as discussed further below. The condition under “when" establishes the trigger of the interaction. The trigger can be a condition on the state of the participants, a request for a particular service, or an event on one of the participants (events are not curently supported in the CDE). Several conditions, identified as "trigger conditions", can be placed in the “when" clause. If one of such conditions is not satisfied, the contract is considered as being “inactive" and, as a result, either the original code of the trigger or another contract is executed (if such a contract exist and is active). This mechanism provides the ability for controlling which of the contracts imposed on a component will be responsible for coordinating it. The set of actions identifies the reactions to be performed, usually in terms of actions of the partners and some of the contracts own actions. When the trigger corresponds to the invocation of an operation, three types of actions may be superposed on the execution of the operation:
It should be noted
that the semantics of contracts allow for only one “replace" clause
to be executed, thus preventing the undesirable situation of having
two alternative actions for the same trigger. Furthermore, any such
replacement action must adhere to whatever specification clauses apply
to the operation (e.g. contracts in the sense of B.Meyer specifying
pre- and post-conditions). This ensures that the functionality of the
original operation, as advertised through its specification, is preserved. The set of actions that are executed as a reaction to a trigger is called the synchronisation set associated with that trigger. The semantics of contracts requires that this set be executed atomically, guarded by the conjunction of the guards of the individual actions together with the conditions included in the "with" clause. Therefore, the “with" clause ("guard conditions") puts further constraints on the execution of the actions involved in the interaction. If any condition under the “with" clause is not satisfied, the synchronisation set fails and none of its actions is executed (including the trigger operation on the participant). In order to illustrate
the use of contracts, consider the familar world of bank accounts in
which clients can, as usual, make withdrawals. The object class account
is usually specified with an attribute balance
and a method withdrawal
with parameter amount.
In a typical implementation one would assign the guard Balance>=amount
restricting this method to occur in states in which the amount to be
withdrawn can be covered by the balance. However assigning this guard
to withdrawal operations can be seen as part of the enforcement of a
business requirement and not necessarily of the functionality of a basic
business entity like account.
Indeed, the circumstances under which a withdrawal is accepted can change
from customer to customer and, even for the same customer, from one
account to another depending on its type. Inheritance is not
a good way of changing the guard in order to model these different situations.
Firstly, inheritance views objects as white boxes in the sense that
adaptations like changes to guards are performed on the internal structure
of the objects, which from the evolution point of view is not desirable.
Secondly, from the business point of view, the adaptations that make
sense may be required on classes other than the ones in which the restrictions
were implemented. In our example, this is the case when it is the type
of client, and not the type of account, that determines the nature of
the guard that applies to withdrawals. The reason the guard will end
up applied to withdrawal,
and the specialization to account,
is that, in the traditional clientship mode of interaction, the code
is placed on the supplier class. Therefore, it makes
more sense for business requirements of this sort to be modeled explicitly
outside the classes that model the basic business entities, because they
represent aspects of the domain that are subject to frequent changes (evolution).
Our proposal is that guards like the one discussed above should be modeled
as coordination contracts that can be established between clients and
accounts: The constraint means
that instances of this contract can only be applied to instances of
Customer that own the
corresponding instance of Account.
The coordination rule superposes the guard that restricts withdrawals
to states in which the balance is greater than the requested amount. Having externalized
the ‚business rule“ that determines the conditions under which withdrawals
can be made, we can support its evolution by defining new contracts that
can replace the old ones when customers subscribe new account packages.
For instance, consider the following contract that allows for relaxing
the functionality of withdrawal
to situations in which an account may become overdrawn up to an agreed
credit limit: The
situation in which a customer decides to subscribe to this new business
rule is modelled by replacing, in the current configuration, the Standard_Withdrawal
contract that connects him/her to the account, by a VIP_withdrawal. This
substitution can be performed at run-time, without interrupting the service
provided by the system, and without requiring the code that implements
withdrawals to be changed. Moreover, the fact that the business rule has
been externalised and made explicit in the configuration leads to a system
architecture that reflects directly the business model, which makes it
much easier to locate and perform changes in the system that derive from
the evolution of the business domain.
b. How For this approach to be effective, we need a means of translating contracts directly to the implementation level. To enable this, the contracts micro-architecture allows coordination contracts to be directly implemented using OO languages, while providing the following advantages:
It
should, also, be noticed that the methodology and technology of coordination
contacts are language independent. However, there is also
no doubt that we need specific language implementations that will allow
for specifying contracts in a level that is closer to the underlying language
and on top of specific implementations of components. The current version
of the CDE provides this functionality for Java as the target language
and with components (contract participants) for which the source code
is available. In future releases, the CDE will support participants as
Java .class files (byte code) as well as C++ as target language. Section Syntax Reference describes how contracts can be edited in the CDE to allow the implementation of the micro-architecture in Java. In what follows, we present how the outlined engineering methodology and contracts can be put together in practice.
3. Putting everything together To put our methodology and coordination contracts together, we propose the adoption of a development strategy based on the following steps:
For examples on how to apply contracts consult the Case Studies section.
|