Formal Refinement of Software Architectures Based on ... - CiteSeerX

2 downloads 160 Views 64KB Size Report
Any architectural design is aiming to produce an executable software, but the ... facilitating software development, using transformation patterns based on ...
Formal Refinement of Software Architectures Based on Rewriting Logic Thomas Bolusset and Flavio Oquendo LLP/ESIA Laboratory, University of Savoie ESIA, B. P. 806, 74016 Annecy Cedex - FRANCE {bolusset, oquendo}@esia.univ-savoie.fr

Abstract. As the end-users’ expectations increase, software systems grow in size and complexity. To avoid design errors that are expensive when detected during the implementation phase, a high level model of the system is needed, i.e. the software architecture. Any architectural design is aiming to produce an executable software, but the manual application code generation may introduce errors. However, a few software architecture description languages propose refinement mechanisms leading from an abstract architectural specification to a more complete, more detailed description of the system. This paper presents a new and formal approach of architecture refinement facilitating software development, using transformation patterns based on rewriting logic.

1 Introduction As costumers expect more and more capabilities from the software product they buy, developers have increasing difficulties to reach good reliability and time-to-market performances. Design errors reveal especially expensive when detected after the implementation phase, so a high level model of the system, the software architecture, is crucial to verify the system properties during the early phases of the development. Moreover, any architectural design is aiming to produce an executable software, but the manual application code generation from the specification may introduce errors between the architecture and its implementation. So an other way to alleviate development costs (and duration) is based on architectural refinement, in order to ensure that the concrete software specifications still meet the requirements. However, a few architecture description languages integrate a mechanism which will permit them to make a system specification evolve from an abstract level to a concrete level, and the type of refinement that they propose is often very limited. On the one hand, some languages allow the automatic generation of the system code while any specification is already constrained by implementation details, and on the other hand some provide a more elaborated support for refinement without permitting to automatically generate the application. Section 2 will depict some typical examples of refinement trends in software architecture. We will then highlight essential requirements of architectural refinement, and sketch out in a further section a new approach of software architecture refinement which stands apart from traditional architecture description languages. An illustration on a short example will then follow. Finally, the last section will be dedicated to some conclusions and perspectives.

2 Separation of Concerns Even though all architecture description languages embed software architecture decomposition, only a few ones are able to make an architecture refinement through different abstraction levels[10]. On the one hand, some languages allow the automatic generation of the code of a system, but in fact specifications are already constrained by implementation details. On the other hand, languages like Rapide[9] and SADL[13] provide a more elaborated support to refinement by establishing mappings between architectural descriptions of different abstraction levels, but they do not permit the automatic generation of the application code. While they do not manipulate exactly the same concepts, other software engineering methodologies use refinement to relate a system specifications from different abstraction levels. The B method[1], for example, is a semantically well founded and structurally rich methodology for formal software development across the whole software life-cycle. Even if it is not directly concerned with software architecture, its syntactic structure permits the expression of refinement relations between specifications (abstract machines) and implementations. In the B method, refinement is based on observational substitutability: any behavior of the refined specification is one of the possible behaviors of the initial specification. This intends to reduce the non-determinism of operations, to weaken their preconditions and to change the variable space. A refinement module in B is defined as a differential to be added to a B module; it can have proper variables which are linked to variables of the refined module by a gluing invariant, and in that case the operations must be stated on the new variables. Recent works about B include retrenchment[2]: the concrete machine can there do less than the abstract one, but it is more specific, more specialized (this is closer in the spirit to architectural refinement). Retrenchment has been designed in order to be less restrictive than the classical refinement in B: one can add operations or parameters, informations can move between the input/output aspects and the state of an operation, and the gluing invariant is separated from the local

invariant. Anyway, the abstract and concrete systems are incompatible, according to the classical B method, even if the operation results must be compatible. Each one of these different approaches uses a specific refinement mechanism. Refinement in Rapide is based on the comparison of architecture simulations: the sets of the generated events, their causal history and their timing. Event patterns are used to define mappings between architectures because they permit hierarchical and comparative simulations. But this kind of architectural refinement rather looks like a conformance test. SADL is interested in the architectural structure of a system and permits to define reusable refinement patterns. A system in SADL is completely described (using one or more styles) and refinement can serve to transform one style into another, thus achieving some kind of genericity. But it is impossible to represent semantic details and both architectures mapped by the refinement must allow to perform the same posets. The B method is not dealing directly with architectural concepts but with abstract machines. Anyway, it provides a formal process based on proof obligations. Refinements in B consist essentially in the modification or replacement of the operation bodies and there is no possibility to reuse them. Refinement can thus take very different aspects. Usually, people make a distinction between vertical and horizontal architectural refinements. On the one side, horizontal refinement covers generally the architectural decomposition: it involves the addition of specification details, while using the same architecture description language. On the other side, vertical refinement brings closer to the implementation by going from a first architecture description language to a second one (possibly an implementation language). It permits to add details which are necessary to obtain an executable system without changing the specification; generally, it amounts to adding details which cannot be specified with the architecture description language of the "upper level". Some software architecture description languages have at their disposal a decomposition system and some have a vertical refinement system. Unfortunately, they usually do not clearly state what are their vertical refinements constructions and what are their horizontal ones. If the constructions of vertical and horizontal refinements were separated, then it would become easier to distinguish what comes under architectural composition and what comes under vertical refinement. In this context, it would be more clear how to take an interest in the refinement of all or a part of the specification and how to verify the system properties. We can also make a distinction concerning the way refinement is performed. For example, SADL replaces the whole architecture when performing its refinement, while B makes a link: the abstract machine is kept unchanged, but it is extended using a differential specification.

3 Refining Software Architectures Decomposition, or horizontal refinement, thus induces a specification modification. So there is no change of abstraction level. As it is not possible to verify what is not written yet, constraints can hardly take place to manage a link between the two specifications on the basis of the refined parts. As a consequence, horizontal refinement would be better represented by a substitution, that is, the original architecture should be explicitly replaced by the new, more detailed architecture. Vertical refinement may not be treated the same way since it consists in specification transformation: the original abstract architecture semantics is not modified but details are added to remove a part of indeterminism or to facilitate implementation. In this case, replacing the architectures would be inadequate and maybe overwhelming since the original specification is not questioned and its behavior is to be strictly respected by the refining architecture. Then a kind of link, like in the B method, should be preferable. Our proposition consists in separating vertical and horizontal refinements constructions in order to make a clear distinction between what is decomposition responsibility and what is concerned by abstraction level change. On the one hand, classical architecture description languages usually support decomposition, but seldom (vertical) refinement. So we intend to achieve the separation of concerns by using the mechanisms of an expressive architecture description language for the decomposition aspects, and a dedicated and separate (but not completely independent) language to deal with vertical refinement. On the other hand, as the architecture refinement’s aim is to lead from an abstract architecture to a concrete one, abstract and concrete architecture description languages may be different: in SADL, the vocabulary of the abstract style is replaced by the vocabulary of the concrete style. In this context, the refinement description language constructions are used to eliminate, little by little, the vocabulary of the starting abstract architecture description language and to introduce the one of the target concrete language which may facilitate the application code generation. The main principles of the refinement language are thus translations between the respective constructs and assembly primitives of the architecture description languages, and the conservation of the essential properties of the system. We may choose as the abstract formalism an architecture description language with an expressive power that is great enough to point out the properties of the system early during the development. π-SPACE[7], for instance, is a formal architecture description language which is able to both provide proof mechanisms for the critical properties of the modeled system, and to represent dynamic software architectures at an abstract level, unlike most other specification languages[4]. As refinement has to lead to the code generation, the concrete language could be either an implementation language or a specification language which allows to automate (at least partially) the generation of the application code. But the

use of an implementation language would disallow the preservation of the abstract architectural properties, since it would not be possible to verify them. Ideally, we should then select a formalism that would enable us to both generate easily the application code and verify the properties of the concrete architectural representation. The B method is quite well adapted in this context. Transformation patterns arise as natural means to conduct an incremental and reusable process from abstract software architecture descriptions to concrete specifications. Such a mechanism allows, amongst other things, to verify solely that the patterns preserve the architectural properties, instead of doing this every time we perform a refinement step. As a consequence, we need to provide a transformation syntax with a sound semantic basis, i.e. a formalism which permits both the expression of generic specification modifications and the study of the conservation of the semantic properties between the abstract and concrete descriptions. Rewriting logic[11] fits perfectly these requirements: we can use it either as a logical framework, to express the way each elementary transformation is performed, or as a semantic framework, to prove that the behavior of the abstract architecture is respected during the whole refinement process.

4 Applying Rewriting Patterns for Refining Software Architectures We may view how this is intended to work using a simple example. The Writer/Checker system[15] is a software system involving one person who writes an artifact, say a module, and another who must check it; they need to interact until the artifact has been validated. As the complete development description cannot adequately fit the size of this publication, we only present here an excerpt. The entire specifications will be the object of a subsequent article. The π–SPACE architectural description of the system stands for the abstract specification, and it exhibits a behavioral semantics based on the π–Calculus process algebra[12]. Its translation into B machines is not obvious; thus, we proceed step by step. Each transformation pattern is intended to act on different parts of the original architecture. define port type Request[receive: [Module], send: [Module]] { … } define port type Reply[send: [Module], receive: [Module]] { … } define port type Request[send: [Module], receive: [Module]] { … } define port type Reply[receive: [Module], send: [Module]] { … } define behaviour component type Write[request_check: Request[send: [Module], receive: [Module]]] { module: Module, reply: Module, do_init[out[Module]] { ... }, do_write1[inout[Module]] { ... }, Write[request_check] = ( (do_init[module] ¤ request_check @ send ¤ request_check @ receive (reply) ¤ write_process[request_check, reply]) + $), write_process[request_check: Request, module: Module] = ( (do_write1[module] ¤ request_check @ send ¤ request_check @ receive (reply) ¤ write_process[request_check, reply]) + $) } define behaviour component type Check[supply_check: Reply[receive: [Module], send: [Module]]] { module: Module, reply: Module, do_check1[in[Module], out[Module]] { ... }, Check[supply_check] = ( (supply_check @ receive (module) ¤ do_check1[module, reply] ¤ supply_check @ send ¤ Check[supply_check]) + $) } define behaviour connector type Switch[caller: Reply[send: [Module], receive: [Module]], callee: Request[receive: [Module], send: [Module]]] { module: Module, reply: Module, Switch[caller, callee] = ( (caller @ send (module) ¤ callee @ receive ¤ callee @ send (reply) ¤ caller @ receive ¤ Switch[caller, callee]) + $) } define component type Writer1[request_check: Request[send: [Module], receive: [Module]]] { port request_check: Request[send: [Module], receive: [Module]] || behaviour write: Write[request_check: Request[send: [Module], receive: [Module]]] } define component type Checker1[supply_check: Reply[receive: [Module], send: [Module]]] { port supply_check: Reply[receive: [Module], send: [Module]] || behaviour check: Check[supply_check: Reply[receive: [Module], send: [Module]]] } define connector type Switcher1[caller: Reply[send: [Module], receive: [Module]], callee: Request[receive: [Module], send: [Module]]] { port caller: Reply[send: [Module], receive: [Module]] || port callee: Request[receive: [Module], send: [Module]] || behaviour switch: Switch[caller: Reply[send: [Module], receive: [Module]], callee: Request[receive: [Module], send: [Module]]] } compose W1P1C1 { W1: Writer1[request_check: Request[send: [Module], receive: [Module]]] || P1: Switcher1[caller: Reply[send: [Module], receive: [Module]], callee: Request[receive: [Module], send: [Module]]] || C1: Checker1[supply_check: Reply[receive: [Module], send: [Module]]] where attach W1 @ request_check to P1 @ caller, attach C1 @ supply_check to P1 @ callee }

Figure 1: the W1P1C1 π–SPACE specification The W1P1C1 π–SPACE architecture consists of an instance W1 of component type Writer1, representing the writer, an instance C1 of component type Checker1, the checker, and an instance P1 of connector type Switcher1 (to enable the communication between the components). They are composed by attaching the port request_check of W1 to the port

caller of P1, and the port supply_check of C1 to the port callee of P1. A substantial extract of the π–SPACE specification of W1P1C1[7] can be found in figure 1. Provided the correctness of the π–SPACE specification has been proven, we can assume that the attached ports are consistent, and that within a connector, ports are consistent with the behavior. The first transformation step can thus concern the attachment informations: references to the connector ports are replaced by the corresponding component ports references (figure 2). define port type Request[receive: [Module], send: [Module]] { … } define port type Reply[send: [Module], receive: [Module]] { … } define port type Request[send: [Module], receive: [Module]] { … } define port type Reply[receive: [Module], send: [Module]] { … } define behaviour component type Write[request_check: Request[send: [Module], receive: [Module]]] { module: Module, reply: Module, do_init[out[Module]] { ... }, do_write1[inout[Module]] { ... }, Write[request_check] = ( (do_init[module] ¤ request_check @ send ¤ request_check @ receive (reply) ¤ write_process[request_check, reply]) + $), write_process[request_check: Request, module: Module] = ( (do_write1[module] ¤ request_check @ send ¤ request_check @ receive (reply) ¤ write_process[request_check, reply]) + $) } define behaviour component type Check[supply_check: Reply[receive: [Module], send: [Module]]] { module: Module, reply: Module, do_check1[in[Module], out[Module]] { ... }, Check[supply_check] = ( (supply_check @ receive (module) ¤ do_check1[module, reply] ¤ supply_check @ send ¤ Check[supply_check]) + $) } define behaviour connector type Switch[request_check: Request[send: [Module], receive: [Module]], supply_check: Reply[receive: [Module], send: [Module]]] { module: Module, reply: Module, Switch[request_check, supply_check] = ( (request_check @ send(module) ¤ supply_check @ receive 〈module〉 ¤ supply_check @ send (reply) ¤ request_check @ receive 〈reply〉 ¤ Switch[request_check, supply_check]) + $) } define component type Writer1[request_check: Request[send: [Module], receive: [Module]]] { port request_check: Request[send: [Module], receive: [Module]] || behaviour write: Write[request_check: Request[send: [Module], receive: [Module]]] } define component type Checker1[supply_check: Reply[receive: [Module], send: [Module]]] { port supply_check: Reply[receive: [Module], send: [Module]] || behaviour check: Check[supply_check: Reply[receive: [Module], send: [Module]]] } define connector type Switcher1[request_check: Request[send: [Module], receive: [Module]], supply_check: Reply[receive: [Module], send: [Module]]] { port request_check: Request[send: [Module], receive: [Module]] || port supply_check: Reply[receive: [Module], send: [Module]] || behaviour switch: Switch[request_check: Request[send: [Module], receive: [Module]], supply_check: Reply[receive: [Module], send: [Module]]] } compose W1P1C1 { W1: Writer1[request_check: Request[send: [Module], receive: [Module]]] P1: Switcher1[request_check: Request[send: [Module], receive: [Module]], supply_check: Reply[receive: [Module], send: [Module]]] C1: Checker1[supply_check: Reply[receive: [Module], send: [Module]]] }

|| ||

Figure 2: the refined W1P1C1 specification This first refinement step is described using 21 rules and 33 equations in rewriting logic. While these are not all required for the transformation of our specific software architecture description, they constitute a pattern which can be applied to every system architecture. In addition, we can establish observational equivalence between specifications related by this transformation pattern: transformations do not affect the sequence nor the nature of the actions in the component and connector behaviours. Subsequent transformation patterns can be sketched out as follows. Component ports have to be consistent with respect to the component behavior; hence the valuable informations left in the port types by the second transformation pattern amount to the communication channels permitting synchronization and message transmission between components and connectors. do_init, do_write1 and do_check1 refer to internal functionalities of components or connectors. Moreover, the component and connector behavior specifications in π–SPACE are based on "π–calculus processes" declarations. One can use a refinement step to clarify their declarations. The total refinement of the processes is achieved through the expansion of the communication channels specification. We shall represent each communication channel by a pair of operations which both access a same variable: one operation ensures the change of the value and the other permits its consultation. As the scope of the communication channels is not limited to a single component or connector, their specification is stored in a separate part of the architectural description; this is also the place in which data types (as they characterize the communication channels) are declared. Afterwards these specifications are imported whenever they are needed.

5 Conclusion As a matter of fact, the complete software development life-cycle involves very different concepts and abstraction levels which may not be represented using a unique formalism. Software architecture description languages exist that are able to specify the system and its essential properties at a given level appropriately, but they are not adapted to manage the transitions between different levels and the final generation of executable code. In the suggested approach, a dedicated refinement description language is used to transform the basic constructs of an expressive existing architecture description language (namely π–SPACE) into elements of a "concrete" formalism based on constructs of the B method. Unfortunately, the latter does not deal with the component-based abstractions occurring in a software architecture description. Recent works like [6] and [14] have been conducted to add informations to B abstract machines, in order to describe the order in which the operations of a B machine may occur. As the demand for dynamic and reactive software systems is growing, architecture refinement becomes a key point of component-based software engineering. However, classical architecture description languages, even when they succeed to represent dynamic architecture behavior, usually don't provide the transformation mechanisms which would correctly propagate changes from the specification into the implementation. A preliminary work has tempted to use SADL as a refinement framework[5], but this architecture description language, as we stated before, is not able to manage all aspects of an architecture description, especially the behavioral semantics. In this paper, we proposed to use a refinement description language to cope with such limitations. In particular, we define transformation patterns in rewriting logic; these permit to elaborate a reusable refinement process preserving observational equivalence. A support tool for architecture refinement is being elaborated based on Maude[8], a rewriting logic system. This tool is intended to allow the model-checking of the abstract architecture in π–SPACE and its progressive refinement. The presented example has been quite simplified to fit the size of this publication, but its complete processing is available in a research report[3]. It describes more precisely the semantics of our refinement description language, its semantics and treatment in rewriting logic, and the way the corresponding B project (abstract machines and implementations) is generated. Future works include the validation of this proposition on other significant industrial problems and the study of the impact of recent extensions of the B method (like event systems).

References [1] J.-R. Abrial, "The B-Book: Assigning programs to meanings", Cambridge University Press, 1996. [2] R. Banach and M. Poppleton, "Retrenchment: An Engineering Variation on Refinement", Proceedings of B’98, LNCS 1393: 129-147, D. Bert (ed.), 1998. [3] T. Bolusset, Raffinement Formel d’Architectures Logicielles Basé sur la Logique de Réécriture : Langage et Système, Research Report, LLP/ESIA, University of Savoie, January 2002 (in French). [4] T. Bolusset, C. Chaudet and F. Oquendo, "Description d’architectures logicielles dynamiques : langage formel et applications industrielles", Génie Logiciel, 53 : 44-50, June 2000 (in French). [5] T. Bolusset, L. Da Silva and F. Oquendo, "Développement à base de composants : une approche centrée architecture pour le raffinement et l'implémentation de logiciels en Java Beans", In Proceedings of ICSSEA’2000, CNAM, Paris, December 2000 (in French). [6] M. Butler, csp2B: A Practical Approach To Combining CSP and B, Technical Report DSSE-TR-99-2, Declarative Systems and Software Engineering Group, February 1999. [7] C. Chaudet and F. Oquendo, "π-SPACE: A Formal Architecture Description Language Based on Process Algebra For Evolving Software Systems", Proceedings of the 15th IEEE International Conference on Automated Software Engineering (ASE’ 00), Grenoble, September 2000. [8] M. Clavel et al., Maude: Specification and Programming in Rewriting Logic, Maude system documentation, March 1999. [9] D. C. Luckham et al., "Specification and analysis of system architecture using Rapide", IEEE Transactions on Software Engineering, 21 (4) : 336-355, April 1995. [10] N. Medvidovic and R. N. Taylor, "A Classification and Comparison Framework for Software Architecture Description Languages", IEEE Transactions on Software Engineering, January 2000. [11] J. Meseguer, "Research Directions in Rewriting Logic", Computational Logic, NATO Advanced Study Institute, 1998. [12] R. Milner, J. Parrow and D. Walker, "A calculus of mobile processes", Journal of Information and Computation, 100 : 1-77, 1992. [13] M. Moriconi and R. A. Riemenschneider, "Correct Architecture Refinement", IEEE Transactions on Software Engineering, 21 (4), April 1995. [14] H. Treharne and S. Schneider, "Using a Process Algebra to Control B OPERATIONS", Proceedings of IFM’99, Springer, York, 1999. [15] B. C. Warboys et al., "Collaboration and Composition: Issues for a Second Generation Process Language", Proceedings of the European Software Engineering Conference (ESEC’99), September 1999.