Formal Modeling and Analysis of Software Architecture: Components, Connectors, and Events David Garlan Carnegie Mellon University, Pittsburgh PA 15213, USA, [email protected] Abstract. Developing a good software architecture for a complex system is a critically important step for insuring that the system will satisfy its principal objectives. Unfortunately, today descriptions of software architecture are largely based on informal “box-and-line” drawings that are often ambiguous, incomplete, inconsistent, and unanalyzable. This need not be the case. Over the past decade a number of researchers have developed formal languages and associated analysis tools for software architecture. In this paper I describe a number of the representative results from this body of work. 1 Introduction The field of software architecture is concerned with the design and modeling of systems at a level of abstraction that reveals their gross structure and allows one to reason about key system properties, such as performance, reliability, and security. Typically architectural modeling is done by describing a system as a set of interacting components, where low-level implementation details are hidden, and relevant high-level system level properties (such as expected throughputs, latencies, and reliabilities) are exposed [29, 32]. Software architecture can be viewed as a level of design and system modeling that forms a bridge between requirements and code. By providing a high-level model of system structure it permits one to understand a system in much simpler terms than is afforded by code level structures, such as classes, variables, methods, and the like. Moreover, if characterized properly an architectural description should in principle allow one to argue that a system’s design satisfies key requirements by appealing to abstract reasoning over the structure. Finally, an architecture forms a blueprint for implementations, indicating what are the principle loci of computation and data storage, the channels of communication, and the interfaces through which communication takes place. To illustrate with a simple example, consider a simple pipelined dataflow architecture, in which streams of data are processed in linear fashion by a sequence of stream transformations, or “filters.” When annotated with properties such as rates of processing, buffering capabilities of the channels, and expected input rates, one can typically reason about expected throughput and latency of M. Bernardo and P. Inverardi (Eds.): SFM 2003, LNCS 2804, pp. 1–24, 2003. c Springer-Verlag Berlin Heidelberg 2003 2 David Garlan the overall system. Additionally, the architectural structure likely mirrors the implementation structures For example, each filter might be implemented as a separate process communicating over buffered, asynchronous channels provided by the operating system. Software architecture consequently plays a critical role in almost all aspects of the software development lifecycle. Requirements specification: Architectural design allows one to determine what one can build, and what requirements are reasonable. Often an architectural sketch is necessary to assess product viability. For example, a preliminary architectural design might tell one whether subsecond response time is a feasible requirement on a new client-server system. System design: Software architecture is a form of high-level system design. It typically determines the first, and most critical, system decomposition. A system without a well-conceived architecture is doomed to failure. Implementation: As noted, an architecture is often the blueprint for low-level design and implementation. The components in an architectural description typically represent subsystems in the implementation, where the architectural interfaces correspond to the interfaces provided by an implementation. Reuse: Most systems exhibit regular structures that represent instances of reusable idioms. For example signal processing systems are often designed as stream processing systems. Data-centric information systems are often designed as 3-tiered client-server systems. More generally, software architectures are a key component of product lines and frameworks. Those systems exploit architectural (and coding) regularities across a family of systems to make it possible to design and create new systems at low cost by specializing a general framework to create a particular product. Maintenance: Software architectures facilitate maintenance by clarifying the system design, and enabling maintainers to understand the impact of changes. Since maintenance can account for well over half of a system’s lifetime costs, and a substantial portion of maintenance is simply understanding a system in order to make a desired change, software architectures can be play a significant role in maintenance. Run time adaptation: Increasingly systems are expected to operate continuously. Automated mechanisms for detecting and repairing system faults while a system is running will likely become essential capabilities in future systems. Software architecture can play an key role in supporting self adaptation, by providing a reflective model that can be used as a basis for automated repair. Unfortunately, the potential uses of software architecture are thwarted by today’s relatively informal approaches to architectural representation, documentation, and analysis. Architectural designs are, more often than not, simply informal “box-and-line” diagrams accompanied by prose. While these representations remain useful to practitioners [31] they suffer from their imprecision. Generally, it is not possible to use them for analysis, to determine with confidence whether some property holds of a system, whether a design is complete or consistent, Formal Modeling and Analysis of Software Architecture 3 whether an implementation conforms to an architectural design, or whether a proposed change violates an architectural principle. In an effort to improve this situation many researchers have proposed formal notations and tools to set architectural design on a more solid engineering footing. Indeed, over the past decade dozens of architectural description languages (ADLs), numerous architectural evaluation methods, and many architectural analysis tools have been proposed by researchers [14, 23]. In the remainder of this paper, we outline some of the ways in which formal methods and notations can be brought to bear on software architecture. We begin with a brief introduction to software architecture. Next we consider various formal approaches to modeling and analyzing architectures. Then we briefly consider automated support, and conclude by listing some of the more interesting open research problems. 2 Software Architecture Before characterizing ways in which we can apply formal modeling and analysis to software architecture, it is important to be clear about what we mean by the term. Definitions of software architecture abound. (The Software Engineering Institute’s Web site catalogs more than 90 definitions [8].) A typical one is the following: The structure or structures of the system, which comprise software components, the externally visible properties of those components, and the relationships among them [6]. Unfortunately, as with most definitions of software architecture, this one begs the questions: What structures? What is a component? What kinds of relationships are relevant? What is an externally visible property? In practice there are a number of kinds of structural decompositions of a system [8, 18]. Each of these has a legitimate place in the design and description of a complex software system, and each has its associated uses with respect to modeling and analysis. One of these is a code decomposition, in which the primary elements are code modules (classes, packages, etc.). Relationships between these elements typically determine code usage and functionality relationships (imports, calls, inherits-from, etc.). Typical analyses include dependency analysis, portability analysis, reuse analysis. A second class of decomposition characterizes the run-time structures of a system. Elements in such descriptions include the principal components of a system that exist as a system is running (clients, servers, databases, etc.). Also important in such descriptions are the communication channels that determine how the components interact. Relationships between these elements determine which components can communicate with each other and how they do so. Analyses of these structures address run-time properties, such as potential for deadlocks and race conditions, reliability, performance, and security. Whether a particular 4 David Garlan analysis can be performed will usually depend on the kind of system. For example, a queueing theoretic analysis might only be valid for a system composed of components that process streams of requests submitted by clients. Or, a schedulability analysis might only be valid for a system in which each component is treated as a periodic process. Other structural representations might emphasize the physical context in which a system is deployed (processors, networks etc.), or developed (organizational teams or business units). In this paper we focus on the second of these classes of structure: run-time decompositions emphasizing the principal computational elements and their communication channels. Sometimes this is referred to as the “component and connector” viewtype [8]. Indeed, in what follows, unless otherwise indicated, when we refer to the software architecture a system, we will mean a component and connector architectural view of it. While systems can in principle be described as arbitrary compositions of components and connectors, in practice there are a number of benefits to constraining the design space for architectures by associating an architectural style with the architecture. An architectural style typically defines a vocabulary of types for components, connectors, interfaces, and properties together with rules that govern how elements of those types may be composed. Requiring a system to conform to a style has many benefits, including support for analysis, reuse, code generation, and system evolution [11, 34, 7]. Moreover, the notion of style often maps well to widely-used component integration infrastructures (such as EJB, HLA, CORBA), which prescribe the kinds of components allowed and the kinds of interactions that may take place between them. 3 Formal Approaches to Software Architecture Since architectural description is a multi-faceted problem, it is helpful to classify the properties of interest into several broad categories: Structure: What are the principal components and the connectors that allow those components to communicate? What kinds of interfaces do components provide? What are the boundaries of subsystem encapsulation? Do the structures conform to any constraints on topology? Is the design complete? Design Constraints: What design decisions should not change over time? What assumptions are being made that should be preserved in the face of future modification, or dynamically evolving architectures? Style: What are the constraints implied by the architectural style? Does a given system conform to constraints of a given architectural style? What analyses are appropriate for a particular architectural style. What are the relationships between different architectural styles? Is it possible to combine two styles to produce a third one? Behavior: What is the abstract behavior of each of the components? What are the protocols of communication that are required for two components to interact? Are the components behaviorally compatible? How does a system Formal Modeling and Analysis of Software Architecture 5 evolve structurally over time? Can we guarantee that all possible structures that emerge at run time will satisfy some property? Refinement: Does a more detailed representation, and in particular a concrete implementation, respect the structure and properties of an architectural design? Let us now consider how formal representations of software architecture can address many of these questions. 3.1 Formalizing Architectural Structure Over the past decade there has been considerable research devoted to the problem of providing more precise ways to characterize the structure of software architectures, and to derive properties of those structures. Indeed, more than a dozen Architecture Description Languages (or ADLs) have been proposed. These notations usually provide both a conceptual framework and a concrete syntax for modeling software architectures. They also typically provide tools for parsing, unparsing, displaying, compiling, analyzing, or simulating architectural descriptions written in their associated language. Examples of ADLs include Aesop [11], Adage [9], C2 [22], Darwin [20], Rapide [19], SADL [26], UniCon [30], Meta-H [7], and Wright [4]. While all of these languages are concerned with architectural design, each provides certain distinctive capabilities: Adage supports the description of architectural frameworks for avionics navigation and guidance; Aesop supports the use of architectural styles; C2 supports the description of user interface systems using an event-based style; Darwin supports the analysis of distributed message-passing systems; Meta-H provides guidance for designers of real-time avionics control software; Rapide allows architectural designs to be simulated, and has tools for analyzing the results of those simulations; SADL provides a formal basis for architectural refinement; UniCon has a high-level compiler for architectural designs that support a mixture of heterogeneous component and connector types; Wright supports the formal specification and analysis of interactions between architectural components. Although there is considerable diversity in the capabilities of different ADLs, all share a similar conceptual basis [23], that determines a common foundation for architectural description. The main elements are: – Components represent the primary computational elements and data stores of a system. Intuitively, they correspond to the boxes in box-and-line descriptions of software architectures. Typical examples of components include such things as clients, servers, filters, objects, blackboards, and databases. In most ADLs components may have multiple interfaces, each interface defining a point of interaction between a component and its environment. – Connectors represent interactions among components. Computationally speaking, connectors mediate the communication and coordination activities among components. That is, they provide the “glue” for architectural 6 David Garlan – – – – designs, and intuitively, they correspond to the lines in box-and-line descriptions. Examples include simple forms of interaction, such as pipes, procedure call, and event broadcast. But connectors may also represent more complex interactions, such as a client-server protocol or a SQL link between a database and an application. Connectors also have interfaces that define the roles played by the various participants in the interaction represented by the connector. Systems represent configurations (graphs) of components and connectors. In modern ADLs a key property of system descriptions is that the overall topology of a system is defined independently from the components and connectors that make up the system. (This is in contrast to most programming language module systems where dependencies are wired into components via import clauses.) Systems may also be hierarchical: components and connectors may represent subsystems that have “internal” architectures. Properties represent semantic information about a system and its components that goes beyond structure. As noted earlier, different ADLs focus on different properties, but virtually all provide some way to define one or more extra-functional properties together with tools for analyzing those properties. For example, some ADLs allow one to calculate overall system throughput and latency based on performance estimates of each component and connector [33]. Constraints represent claims about an architectural design that should remain true even as it evolves over time. Typical constraints include restrictions on allowable values of properties, topology, and design vocabulary. For example, an architecture might constrain its design so that the number of clients of a particular server is less than some maximum value. Styles represent families of related systems. An architectural style typically defines a vocabulary of design element types and rules for composing them [32]. Examples include dataflow architectures based on graphs of pipes and filters, blackboard architectures based on shared data space and a set of knowledge sources, and layered systems. Some architectural styles additionally prescribe a framework1 as a set of structural forms that specific applications can specialize. Examples include the traditional multistage compiler framework, 3-tiered client-server systems, the OSI protocol stack, and user interface management systems. As a very simple illustrative example, consider a simple containing a client and server component connected by a RPC connector. The server itself might be represented by a subarchitecture. Properties of the connector might include the protocol of interaction that it requires. Properties of the server might include the 1 Terminology distinguishing different kinds of families of architectures is far from standard. Among the terms used are “product-line frameworks,” “component integration standards,” “kits,” “architectural patterns,” “styles,” “idioms,” and others. For the purposes of this paper, the distinctions between these kinds of architectural families is less important than the fact that they all represent a set of architectural instances. Formal Modeling and Analysis of Software Architecture 7 average response time for requests. Constraints on the system might stipulate that no more than five clients can ever be connected to this server and that servers may not initiate communication with a client. The style of the system might be a “client-server” style in which the vocabulary of design includes clients, servers, and RPC connectors. This conceptual basis of ADLs provides a natural way to model the runtime architectures of systems. First, ADLs allow one to describe compositions of components precisely, making explicit the ways in which those components communicate. Second, they support hierarchical descriptions and encapsulation of subsystems as components in a larger system. Third, they support the specification and analysis of non-functional properties. Fourth, many ADLs provide an explicit home for describing the detailed semantics of communication infrastructure (through specification of connector types). Fifth, ADLs allow one to define constraints on system composition that make clear what kinds of compositions are allowed. Finally, architectural styles allow one to make precise the differences between kinds of component integration standards. To be concrete, we now describe a representative ADL, called Acme [13] Acme supports the definition of four distinct aspects of architecture. First is structure—the organization of a system as a set of interacting parts. Second is properties of interest—information about a system or its parts that allow one to reason abstractly about overall behavior (both functional and extra-functional). Third is constraints—guidelines for how the architecture can change over time. Fourth is types and styles—defining classes and families of architecture. Structure Architectural structure is defined in Acme using seven core types of entities: components, connectors, systems, ports, roles, representations, and rep-maps. Consistent with the vocabulary outlined earlier, Acme components represent computational elements and data stores of a system. A component may have multiple interfaces, each of which is termed a port. A port identifies a point of interaction between the component and its environment, and can represent an interface as simple as a single procedure signature. Alternatively, a port can define a more complex interface, such as a collection of procedure calls that must be invoked in certain specified orders, or an event multicast interface. Acme connectors represent interactions among components. Connectors also have interfaces that are defined by a set of roles. Each role of a connector defines a participant of the interaction represented by the connector. Binary connectors have two roles such as the caller and callee roles of an RPC connector, the reading and writing roles of a pipe, or the sender and receiver roles of a message passing connector. Other kinds of connectors may have more than two roles. For example an event broadcast connector might have a single event-announcer role and an arbitrary number of event-receiver roles. Acme systems are defined as graphs in which the nodes represent components and the arcs represent connectors. This is done by identifying which component ports are attached to which connector roles. 8 David Garlan Figure 1 contains an Acme description of the simple architecture described above. A client component is declared to have a single send-request port, and the server has a single receive-request port. The connector has two roles designated caller and callee. The topology of this system is defined by listing a set of attachments that bind component ports to connector roles. In this case, the client’s requesting port is bound to the rpc’s caller role, and the servers’s requesthandling port is bound to the rpc’s callee role. System simple_cs = { Component client = { Port sendRequest } Component server = { Port receiveRequest } Connector rpc = { Roles {caller, callee} } Attachments : { client.sendRequest to rpc.caller ; server.receiveRequest to rpc.callee } } Fig. 1. Simple Client-Server System in Acme. To support hierarchical descriptions of architectures, Acme permits any component or connector to be represented by one or more detailed, lower-level descriptions. Each such description is termed a representation. When a component or connector has an architectural representation there must be some way to indicate the correspondence between the internal system representation and the external interface of the component or connector that is being represented. A rep-map (short for “representation map”) defines this correspondence. In the simplest case a rep-map provides an association between internal ports and external ports (or, for connectors, internal roles, and external roles).2 In other cases the map may be considerably more complex. Figures 2 illustrates the use of representations in elaborating the simple client-server example. In this case, the server component is elaborated by a more detailed architectural representation. Properties The seven classes of design element outlined above are sufficient for defining the structure of an architecture as a graph of components and connectors. However, there is more to architectural description than structure. But what exactly? Looking at the range of ADLs, each typically has its own forms of auxiliary information that determines such things as the run-time semantics of the system, protocols of interaction, scheduling constraints, and resource consumption. Clearly, the needs for documenting extra-structural properties of a system’s architecture depend on the nature of the system, the kinds of analyses required, the tools at hand, and the level of detail included in the description. 2 Note that rep-maps are not connectors: connectors define paths of interaction, while rep-maps identify an abstraction relationship between sets of interface points. Formal Modeling and Analysis of Software Architecture 9 System simpleCS = { Component client = { ... } Component server = { Port receiveRequest; Representation serverDetails = { System serverDetailsSys = { Component connectionManager = { Ports { externalSocket; securityCheckIntf; dbQueryIntf } } Component securityManager = { Ports { securityAuthorization; credentialQuery; } } Component database = { Ports { securityManagementIntf; queryIntf; } } Connector SQLQuery = { Roles { caller; callee } } Connector clearanceRequest = { Roles { requestor; grantor } } Connector securityQuery = { Roles { securityManager; requestor } } Attachments { connectionManager.securityCheckIntf to clearanceRequest.requestor; securityManager.securityAuthorization to clearanceRequest.grantor; connectionManager.dbQueryIntf to SQLQuery.caller; database.queryIntf to SQLQuery.callee; securityManager.credentialQuery to securityQuery.securityManager; database.securityManagementIntf to securityQuery.requestor; } } Bindings { connectionManager.externalSocket to server.receiveRequest } } } Connector rpc = { ... } Attachments { client.send-request to rpc.caller ; server.receive-request to rpc.callee } Fig. 2. Client-Server System with Representation. To accommodate the open-ended requirements for specification of auxiliary information, Acme supports annotation of architectural structure with arbitrary lists of properties. Figure 3 shows the simple client-server system elaborated with several properties. In the figure, properties document such things as the client’s expected request rate and the location of its source code. For the rpc connector, properties document the protocol of interaction described as a Wright specification [4] (described in Section 3.4). Properties serve to document details of an architecture relevant to its design and analysis. However, from Acme’s point of view properties are uninterpreted values—that is, they have no intrinsic semantics. Properties become useful, however, when tools use them for analysis, translation, display, and manipulation. 10 David Garlan System simple_cs = { Component client = { Port sendRequest; Properties { requestRate : float = 17.0; sourceCode : externalFile = "CODE-LIB/client.c" }} Component server = { Port receiveRequest; Properties { idempotent : boolean = true; maxConcurrentClients : integer = 1; multithreaded : boolean = false; sourceCode : externalFile = "CODE-LIB/server.c" }} Connector rpc = { Role caller; Role callee; Properties { synchronous : boolean = true; maxRoles : integer = 2; protocol : WrightSpec = "..." }} Attachments { client.send-request to rpc.caller ; server.receive-request to rpc.callee } } Fig. 3. Client-Server System with Properties. 3.2 Formalizing Architectural Design Constraints One of the key ingredients of an architecture model is a set of design constraints that determine how an architectural design is permitted to evolve over time. Acme uses a constraint language based on first order predicate logic. That is, design constraints are expressed as predicates over architectural specifications. The constraint language includes the standard set of logical constructs (conjunction, disjunction, implication, quantification, and others). It also includes a number of special functions that refer to architecture-specific aspects of a system. For example, there are predicates to determine if two components are connected, and if a component has a particular property. Other functions return the set of components in a given system, the set of ports of a given component, the set of representations of a connector, and so forth. Figure 4 lists a representative set of example functions. (For a detailed description see [25].) Constraints can be associated with any design element of an architectural model. The scope of the constraint is determined by that association. For example, if a constraint is attached to a system then it can refer to any of the design elements contained within it (components, connectors, and their parts). On the other hand, a constraint attached to a component can only refer to that compo- Formal Modeling and Analysis of Software Architecture 11 Connected(comp1, comp2) True if component comp1 is connected to component comp2 by at least one connector Reachable(comp1, comp2) True if component comp2 is in the transitive closure of Connected(comp1, *) HasProperty(elt, propName) True if element elt has a property called propName HasType(elt, typeName) True if element elt has type typeName SystemName.Connectors The set of connectors in system SystemName ConnectorName.Roles The set of the roles in connector ConnectorName Fig. 4. Sample Functions for Constraint Expressions. nent (using the special keyword self , and its parts (that is, its ports, properties, and representations). To give a few examples, consider the following constraints that might be associated with a system: connected(client, server) will be true if the components named client and server are connected directly by a connector. Forall conn : connector in systemInstance.Connectors @ size(conn.roles) =2 will be true of a system in which all of the connectors are binary connectors. Forall conn : connector in systemInstance.Connectors @ Forall r : role in conn.Roles @ Exists comp : component in systemInstance.Components @ Exists p : port in comp.Ports @ attached(p,r) and (p.protocol = r.protocol) will be true when all connectors in the system are attached to a port, and the attached (port, role) pair share the same protocol. Here the port and role protocol values are represented as properties of the port and role design elements. Constraints can also define the range of legal property values, as in self.throughputRate >= 3095 and indicate relationships between properties, as in comp.totalLatency = (comp.readLatency + comp.processingLatency + comp.writeLatency) Constraints may be attached to design elements in one of two ways: as an invariant or a heuristic. In the first case, the constraint is taken to be a rule that cannot be violated. In the second case, the constraint is taken to be a rule that should be observed, but may be selectively violated. Tools that check 12 David Garlan for consistency will naturally treat these differently. A violation of an invariant makes the architectural specification invalid, while a violation of a heuristic is treated as a warning. Figure 5 illustrates how constraints might be used for a hypothetical MessagePath connector. In this example an invariant prescribes the range of legal buffer sizes, while a heuristic prescribes a maximum value for the expected throughput. System messagePathSystem = { ... Connector MessagePath = { Roles {source; sink;} Property expectedThroughput : float = 512; Invariant (queueBufferSize >= 512) and (queueBufferSize <= 4096); Heuristic expectedThroughput <= (queueBufferSize / 2); } } Fig. 5. MessagePath Connector with Invariants and Heuristics. 3.3 Formalizing Architectural Style An important general capability for the description of architectures is the ability to define styles—or families—of systems. Styles allow one to define a domainspecific or application-specific design vocabulary, together with constraints on how that vocabulary can be used. This in turn supports packaging of domainspecific design expertise, use of special-purpose analysis and code-generation tools, simplification of the design process, and the ability to check for conformance to architectural standards. The basic building block for defining styles in Acme is a type system that can be used to encapsulate recurring structures and relationships. Using Acme one can define types of components, connectors, ports, and roles. Each such type provides a type name and a list of required substructure, properties, and constraints. Figure 6 illustrates the definition of a Client component type. The type definition specifies that any component that is an instance of type Client must have at least one port called Request and a property called request-rate of type float. Further, the invariants associated with the type require that all ports of a Client component have a protocol property whose value is rpc-client, that no client more than 5 ports, that a component’s request rate is larger greater than 0. Finally, there is a heuristic indicating that the request-rate should be less than 100. Formal Modeling and Analysis of Software Architecture 13 Component Type Client = { Port Request = {Property protocol: CSPprotocolT}; Property request-rate: Float; Invariant Forall p in self.Ports @ p.protocol = rpc-client; Invariant size(self.Ports) <= 5; Invariant request-rate >= 0; Heuristic request-rate < 100; } Fig. 6. Component Type “Client.” An Acme style, or family 3 is defined by specifying a set of types and a set of constraints. The types provide the design vocabulary for the style. The constraints determine how instances of those types can be used. Figure 7 illustrates the definition of a “Pipe and Filter” style, together with a sample system declaration using the style. The style defines two component types, one connector type, and one property type. The single invariant of this family prescribes that all connectors must be pipes. The system simplePF is then defined as an instance of the style. This declaration allows the system to make use of any of the types in the style, and it must satisfy all of the style’s invariants. But what does it mean for an instance to satisfy a type? In Acme, types are interpreted as predicates, and asserting that an instance satisfies a type is the same as asserting that it satisfies the predicate denoted by the type. The predicate associated with a type is constructed by viewing declared structure as asserting the existence of that structure in each instance. In other words, a type defines the minimal structure of its instances.4 (Hence, in the example of Figure 7 it is essential to include the invariant asserting that all connectors have type pipe.) The use of a predicate-based type system has several important consequences. First, design elements (and systems) can have an arbitrary number of types. For example, the fact that a structural element is declared to be of a particular type, does not preclude it from satisfying other type specifications. This is an important property since it permits, for example, a system to be considered a valid instance of a style, even though it was not explicitly declared as such. Second, the use of invariants fits smoothly within the type system. Adding a invariant to a structural type or family simply conjoins that predicate with the others in the type. This means that the type system becomes quite expressive – essentially harnessing predicate logic to create useful type distinctions. 3 4 For historical reasons a “style” in Acme is termed a “family.” The semantics of the Acme type system is similar to – but considerably simpler than – that of other predicate-based type systems, such as the one used by PVS [28]. For a formal treatment of the semantics, see [25]. 14 David Garlan Family PipeFilterFam = { Component Type FilterT = { Ports { stdin; stdout; }; Property throughput : int; }; Component Type UnixFilterT extends FilterT with { Port stderr; Property implementationFile : String; }; Connector Type PipeT = { Roles { source; sink; }; Property bufferSize : int; }; Property Type StringMsgFormatT = Record [ size:int; msg:String; ]; Invariant Forall c in self.Connectors @ HasType(c, PipeT); } System simplePF : PipeFilterFam = { Component smooth : FilterT = new FilterT Component detectErrors : FilterT; Component showTracks : UnixFilterT = new UnixFilterT extended with { Property implementationFile : String = "IMPL_HOME/showTracks.c"; }; // Declare the system’s connectors Connector firstPipe : PipeT; Connector secondPipe : PipeT; // Define the system’s topology Attachments { smooth.stdout to firstPipe.source; detectErrors.stdin to firstPipe.sink; detectErrors.stdout to secondPipe.source; showTracks.stdin to secondPipe.sink; } } Fig. 7. Definition of a Pipe-Filter Family. Third, the process of type checking becomes one of checking satisfaction of a set of predicates over declared structures. Hence, types play two useful roles: (a) they encapsulate common, reusable structures and properties, and (b) they support a powerful form of checkable redundancy. The use of predicates does, however, raise the issue that, in general, checking for satisfaction of predicates is not decidable. Therefore, systems that rely on predicate-based type systems usually do so with the aid of a theorem prover Formal Modeling and Analysis of Software Architecture 15 (for example, PVS [28]). In Acme, however, we constrain the expressiveness of types so that type checking remains decidable by ensuring that quantification is only over finite sets of elements. (Finiteness comes from the fact that Acme structures can only declare a finite number of subparts – components, ports, representations, and others.) 3.4 Formalizing Architectural Behavior In addition to formal modeling of architectural structure, properties, constraints and styles, it is also useful to be able to model and analyze architectural behavior. By associating behavior with architectures, we are able to express much richer semantic models, capturing things such as the fact that a pipe provides buffered, order-preserving data transmission, or that a given component will call the services of another component in some particular order. This in turn allows us to attach analyze important properties, such as system deadlocks, race conditions, and interface incompatibilities. In principle there are many possible ways one might specify behavior of the elements in an architectural model. Indeed, almost any formalism can be used, and researchers have experimented with formal techniques ranging from prepost conditions [1], process algebras [4, 20], statecharts [5], POSets [19], rewrite rules [17], and many others. However, all of these have a similar flavor: (1) they document the individual elements with behavior characterized in terms of abstract events, states and transitions, and (2) they then perform various composition checks or simulations to test for aggregate behavior, mismatches, deadlocks, and other anomalies. Wright. To illustrate how this can be done, consider the Wright architecture specification language [4]. Wright adopts an approach based on the process algebra CSP [16]. Specifically it associates a CSP-like process with each component, each component interface (port), each connector, and each connector interface (role). The overall behavior is then a set of interacting protocols. The notation used is a subset of CSP, containing the following elements: – Processes and Events: A process describes an entity that can engage in communication events.5 Events may be primitive or they can have associated data (as in e?x and e!x, representing input and output of data, respectively). √ The simplest process, STOP, is one that engages in no events. The event is used represent the “success” event. The set of events that a process, P, understands is termed the “alphabet of P,” or αP . – Prefixing: A process that engages in event e and then becomes process P is denoted e→P . 5 It should be clear that by using the term “process” we do not mean that the implementation of the protocol would actually be carried out by a separate operating system process. That is to say, processes are logical entities used to specify the components and connectors of a software architecture. 16 David Garlan – Alternative: (“deterministic choice”) A process that can behave like P or Q, where the choice is made by the environment, is denoted P Q. ( “Environment” refers to the other processes that interact with the process.) – Decision: (“non-deterministic choice”) A process that can behave like P or Q, where the choice is made (non-deterministically) by the process itself, is denoted P Q. – Named Processes: Process names can be associated with a (possibly recursive) process expression. Unlike CSP, however, we restrict the syntax so that only a finite number of process names can be introduced. We do not permit, for example, names of the form Namei , where i can range over the positive numbers. In process expressions → associates to the right and binds tighter than either or . So e→f →P g→Q is equivalent to (e→(f →P )) (g→Q). In addition to this standard notation from CSP we introduce three notational conventions. First, we use the symbol § to represent a successfully terminating √ process. This is the process that engages in the success event, , and then stops. def √ (In CSP, this process is called SKIP.) Formally, § = →STOP. Second, we allow the introduction of scoped process names, as follows: let Q = expr1 in R. Third, as in CSP, we allow events and processes to be labeled. The event e labeled with l is denoted l.e. The operator “:” allows us to label all of the events in a process, so that l : P is the same process as P , but with each of its events labeled. For our purposes we use the variant of this operator that does not label √ . We use the symbol Σ to represent the set of all unlabeled events. This subset of CSP defines processes that are essentially finite state. It provides sequencing, alternation, and repetition, together with deterministic and non-deterministic event transitions. Connector Description. To see how this is used let us consider first how a connector is specified. A connector type is specified by a set of roles processes and a glue process. The roles describe the expected local behavior of each of the interacting parties. For example, the client-server connector illustrated earlier would have a client role and a server role. The client role process might describe the client’s behavior as a sequence of alternating requests for service and receipts of the results. The server role might describe the server’s behavior as the alternate handling of requests and return of results. The glue specification describes how the activities of the client and server roles are coordinated. It would say that the activities must be sequenced in the order: client requests service, server handles request, server provides result, client gets result. This is how it would be written using the notation just outlined. connector Service = role Client = request!x→ result?y → Client § role Server = invoke?x→ return!y → Server § glue = Client.request?x→ Service.invoke!x →Service.return?y→Client.result!y→glue § Formal Modeling and Analysis of Software Architecture 17 The Server role describes the communication behavior of the server. It is defined as a process that repeatedly accepts an invocation and then returns; or it can terminate with success instead of being√invoked. Because we use the alternative operator ( ), the choice of invoke or is determined by the environment of that role (which, as we will see, consists of the other roles and the glue). The Client role describes the communication behavior of the user of the service. Similar to Server, it is a process that can call the service and then receive the result repeatedly, or terminate. However, because we use the decision operator () in this case, the choice of whether to call the service or to terminate is determined by the role process itself. Comparing the two roles, note that the two choice operators allow us to distinguish formally between situations in which a given role is obliged to provide some services – the case of Server – and the situation where it may take advantage of some services if it chooses to do so – the case of Client. The glue process coordinates the behavior of the two roles by indicating how the events of the roles work together. Here glue allows the Client role to decide whether to call or terminate and then sequences the remaining three events and their data. The example above illustrates that the connector description language is capable of expressing the traditional notion of providing and using a set of services – the kind of connection supported by import/export clauses of module interconnection. As another illustration, consider two examples of a shared data connector. connector Shared Data1 = role User1 = set→User1 get→User1 § role User2 = set→User2 get→User2 § glue = User1 .set→glue User2 .set→glue User1 .get→glue User2 .get→glue § connector Shared Data2 = role Initializer = let A = set→A get→A § in set→A role User = set→User get→User § glue = let Continue = Initializer.set→Continue User.set→Continue Initializer.get→Continue User.get→Continue § in Initializer.set→Continue § The first, Shared Data1 , indicates that the data does not require an explicit initialization value. The second, Shared Data2 , indicates that there is a distinguished role Initializer that must supply the initial value. To take a more complex example, consider the following specification of a pipe connector. 18 David Garlan connector Pipe = role Writer = write→Writer close→§ role Reader = let ExitOnly = close→§ in let DoRead = (read→Reader read-eof→ExitOnly) in DoRead ExitOnly glue = let ReadOnly = Reader.read→ReadOnly Reader.read-eof →Reader.close →§ Reader.close→§ in let WriteOnly = Writer.write→WriteOnly Writer.close→§ in Writer.write→glue Reader.read→glue Writer.close→ReadOnly Reader.close→WriteOnly It might appear to be a simple matter to define a pipe: both the writer and the reader decide when and how many times they will write or read, after which they will each close their side of the pipe. In fact, the writer role is just that simple. The reader, on the other hand, must take other considerations into account. There must be a way to inform the reader that there will be no more data. Connector Semantics. The intuition behind a connector description is that the roles are treated as independent processes, constrained only by the glue, which serves to coordinate and interleave the events. To make this idea precise we use the CSP parallel composition operator, , for interacting processes. The process P1 P2 is one whose behavior is permitted by both P1 and P2 . That is, for the events in the intersection of the processes’ alphabets, both processes must agree to engage in the event. We can then take the meaning of a connector description to be the parallel interaction of the glue and the roles, where the alphabets of the roles and glue are arranged so that the desired coordination occurs. Hence, the meaning of a connector description with roles R1 , R2 , . . ., Rn , and glue Glue is the process: Glue (R1 :R1 R2 :R2 . . . Rn :Rn ) where Ri is the (distinct) name of role Ri , and √ αGlue = R1 :Σ ∪ R2 :Σ ∪ . . . ∪ Rn :Σ ∪ { }. In this definition we arrange for the glue’s alphabet to be the union of all possible events labeled by the respective role names (e.g. Client, Server), together Formal Modeling and Analysis of Software Architecture 19 √ with the √ event. This allows the glue to interact with each role. In contrast, (except for ) the role√alphabets are disjoint and so each role can only interact with the glue. √ Because is not relabeled, all of the roles and glue can (and must) agree on for it to occur. In this way we ensure that successful termination of a connector becomes the joint responsibility of all the parties involved. Describing Components. Thus far we have concerned ourselves with the definition of connector types. To complete the picture we must also describe the ports of components and how those ports are attached to specific connector roles in a complete software architecture. In Wright, component ports are also specified by processes: The port process defines the expected behavior of the component at that particular point of interaction. For example, a component that uses a shared data item only for reading might be partially specified as follows: component DataUser = port DataRead = get→DataRead § other ports... Since the port protocols define the actual behavior of the components when those ports are associated with the roles, the port protocol takes the place of the role protocol in the actual system. Thus, an attached connector is defined by the protocol that results from the replacement of the role processes with the associated port processes. More formally, the meaning of attaching ports P1 . . . Pn as roles R1 . . . Rn of a connector with glue Glue is the process: Glue (R1 :P1 R2 :P2 . . . Rn :Pn ). Note that this definition of attachment implies that port protocols need not be identical to the role protocols that they replace. This is advantageous because it allows greater opportunities for reuse. For instance, in the above example, the DataUser component should be able to interact with another component (via a shared data connector) even though it never needs to set. As another example, we would expect to be able to attach a File port as the Reader role of a pipe (as is commonly done in Unix when directing the output of a pipe to a file). But this raises an important question: when is a port “compatible” with a role? For example, it would be reasonable to forbid DataRead to be used as the Initializer role for the Shared Data2 connectors, since it requires an initial set; clearly DataRead will never provide this event. Analyzing Architectural Behavior. Once one has a formal definition of behavior there are a number of analyses that one can perform. The most obvious one is checking that a connector is well-formed. That is to say, that the Glue in combination with the roles does not lead to deadlock. Another useful check is to investigate race conditions. This can be done by checking whether certain events can ever occur out of order. 20 David Garlan Yet another check is to answer questions like “what ports may be used in this role?” At first glance it might seem that the answer is obvious: simply check that the port and role protocols are equivalent. But as illustrated earlier, it is important to be able to attach a port that is not identical to the role. On the other hand, we would like to make sure that the port fulfills its obligations to the interaction. For example, if a role requires an initialization as the first operation (cf., the shared data example), we would like to guarantee that any port actually performs it. Informally, we would like to be able to guarantee that an attached port process always acts in a way that the corresponding role process is capable of acting. This can be recast as follows: When in a situation predicted by the protocol, the port must always continue the protocol in a way that the role could have. In CSP this intuitive notion is captured by the concept of refinement. Roughly, process P2 refines P1 (written P1 P2 ) if the behaviors of P1 include those of P2 . Technically, the definition is given in terms of the failures/divergences model of CSP [16, Chapter 3]. For various technical reasons, however, the actual definition of compatibility is a little more complex to define, although it captures the same essential idea of refinement. (See [4] for details.) As another check, one can investigate whether a port can be left unattached. This can be done by seeing if the port will deadlock when connected to a “do nothing” connector. Other checks are described in detail in [2]. Analyzing Reconfigurable Architectures Thus far the analysis has assumed a static architecture: that is, the structure of the architecture does not change during the execution of a system. While this is often a useful approximation to systems, clearly in the general case systems do evolve structurally. At the very least, during initialization the system must be created, and this is not likely to be an atomic operation. As another example, consider a simple client-server system, such as the one illustrated earlier, but that allows for the possibility that a server may crash. In such cases the system might reconfigure itself so that the client uses a backup server. This can be done by adding a new connector during run time. One of the things we would like to guarantee for such a system is that no client requests are lost. This requires some constraints on when reconfiguration can happen. Some work has been done to address these issues, although comparatively that work is relatively sparse. In our own work we showed how to extend Wright to handle dynamically changing topologies [3]. Others have looked at ways to use the Pi Calculus to specify such things [20]. Others have looked at graph grammars [24] and category-theoretic approaches [35]. Unfortunately, in all of these cases the complexity of the specification becomes drastically higher, and the models become much less tractable for static analysis. Formal Modeling and Analysis of Software Architecture 4 21 Automated Support For all of the formal approaches outlined earlier, researchers have developed numerous tools to aid in the modeling and analysis process for architects. Broadly speaking there are three general categories of tools: 1. Design Assistants: These tools tend to focus on providing a graphical front end to allow architects to develop designs. Typically they provide a pallet of component and connector types that can be instantiated to create system descriptions. Typical examples are environments such as C2 [22], MetaH [7], Aesop [11], and Darwin [20]. 2. Design Checkers: While automated support for architectural creation and browsing is valuable, to be effective one must also provide analysis capabilities. Hence, a number of tools have been created to perform various checks. For example AcmeStudio [25] checks for violations of design constraints. Wright provides a tool for performing the checks outlined earlier. Those checks are based on the use of the FDR [10] model checker for CSP. Kramer and Magee demonstrate how to use their LTSA tool to check specifications written in their process algebra, FSP [21]. 3. Code Generators: In many cases a formal definition of an architecture can be used to generate system code. For example, the UniCon system handles the generation of connector code for a wide variety of connector types [30]. Similarly C2 can generate partial implementations in using various infrastructures to handle component interaction. 5 Conclusion and Future Prospects As we have tried to illustrate, software architecture is a field in which formal modeling and analysis can have a major impact. While the state of practice continues to rely on informal and semi-formal descriptions, considerable research has been done to develop good formal models and associated tools for analyzing them. But the story is far from complete and there a number of areas in which further research is needed. Here are a few. – Scalability: Although some large case studies have been carried out (e.g., [5]), there are relatively few demonstrated success stories for large, complex industrial systems. When systems have thousands of components, it is not clear how well the representation techniques (particularly graphical ones) scale. Nor is it clear whether analyses remain tractable. For example, many analysis tools are based on model checkers, which have significant limitation on the size of the model that can be checked. – Dynamism: As noted earlier a key issue is modeling systems whose structure changes at run time. – Code conformance: One of the big problems is guaranteeing that an implementation conforms to its architectural specification. In situations where a 22 David Garlan code generator is used it is often possible to guarantee conformance by construction. But more generally, given an architecture and body of code, there has been very little work on finding ways to make sure they are consistent. The main problem is that architectures (as we have discussed them) represent run-time models, whereas code is obviously a design-time artifact. In general it is undecidable whether a given body of code will generate a given architecture. There are also some intriguing new directions being explored in the area of self-adaptive systems. Increasingly systems are required to run continuously. Moreover they must often do this in the context of environments whose resources are constantly changing (e.g., wireless bandwidth), or whose components may be changing dynamically (e.g., web services). One approach that is being investigated by a number of researchers is the incorporation of self-adaptation or self-healing into a system. The interesting question is how should one do this? One approach is to use architectural models as the basis for system monitoring and repair [12, 15, 27]. The idea is that the architectural model becomes available at run-time in order to understand whether a system is performing optimally, and if not it can be used model to reason about reasonable repair strategies at a high level of abstraction. While work is just beginning in this area, it appears to be a promising avenue for future research. References [1] Gregory Abowd, Robert Allen, and David Garlan. Formalizing style to understand descriptions of software architecture. ACM Transactions on Software Engineering and Methodology, October 1995. [2] Robert Allen. A Formal Approach to Software Architecture. PhD thesis, Carnegie Mellon, School of Computer Science, January 1997. Issued as CMU Technical Report CMU-CS-97-144. [3] Robert Allen, Rémi Douence, and David Garlan. Specifying and analyzing dynamic software architectures. In Proceedings of the 1998 Conference on Fundamental Approaches to Software Engineering (FASE’98), Lisbon, Portugal, March 1998. [4] Robert Allen and David Garlan. A formal basis for architectural connection. ACM Transactions on Software Engineering and Methodology, July 1997. [5] Robert Allen, David Garlan, and James Ivers. Formal modeling and analysis of the HLA component integration standard. In Proceedings of of the 6th International Symposium on the Foundations of Software Engineering (FSE-6), Lake Buena Vista, Florida, November 1998. ACM Press. [6] L. Bass, P. Clements, and R. Kazman. Software Architecture in Practice. Addison Wesley, 1998. ISBN 0-201-19930-0. [7] Pam Binns and Steve Vestal. Formal real-time architecture specification and analysis. In Tenth IEEE Workshop on Real-Time Operating Systems and Software, New York, NY, May 1993. [8] Paul Clements, Felix Bachmann, Len Bass, David GArlan, James Ivers, Reed Little, Robert Nord, and Judith Stafford. Documenting Software Architectures: Views and Beyond. Addison Wesley, 2002. Formal Modeling and Analysis of Software Architecture 23 [9] L. Coglianese and R. Szymanski. DSSA-ADAGE: An Environment for Architecture-based Avionics Development. In Proceedings of AGARD’93, May 1993. [10] Failures Divergence Refinement: User Manual and Tutorial. Formal Systems (Europe) Ltd., Oxford, England, 1.2β edition, October 1992. [11] David Garlan, Robert Allen, and John Ockerbloom. Exploiting style in architectural design environments. In Proceedings of SIGSOFT’94: The Second ACM SIGSOFT Symposium on the Foundations of Software Engineering, pages 179– 185. ACM Press, December 1994. [12] David Garlan, Shang-Wen Cheng, and Bradley Schmerl. Increasing system dependability through architecture-based self-repair. In A. Romanovsky R. de Lemos, C. Gacek, editor, Architecting Dependable Systems. Springer-Verlag, 2003. [13] David Garlan, Robert T. Monroe, and David Wile. Acme: Architectural description of component-based systems. In Gary T. Leavens and Murali Sitaraman, editors, Foundations of Component-Based Systems, page 47. Cambridge University Press, 2000. [14] David Garlan and Dewayne Perry. Introduction to the special issue on software architecture. IEEE Transactions on Software Engineering, 21(4), April 1995. [15] Ioannis Georgiadis, Jeff Magee, and Jeff Kramer. Self-organising software architectures for distributed systems. In Proceedings of the First ACM SIGSOFT Workshop on Self-Healing Systems (WOSS ’02), 2002. [16] C.A.R. Hoare. Communicating Sequential Processes. Prentice Hall, 1985. [17] Paola Inverardi and Alex Wolf. Formal specification and analysis of software architectures using the chemical, abstract machine model. IEEE Transactions on Software Engineering, Special Issue on Software Architecture, 21(4):373–386, April 1995. [18] P. B. Kruchten. The 4+1 view model of architecture. IEEE Software, pages 42–50, November 1995. [19] David C Luckham, Lary M. Augustin, John J. Kenney, James Veera, Doug Bryan, and Walter Mann. Specification and analysis of system architecture using Rapide. IEEE Transactions on Software Engineering, Special Issue on Software Architecture, 21(4):336–355, April 1995. [20] J. Magee, N. Dulay, S. Eisenbach, and J. Kramer. Specifying distributed software architectures. In Proceedings of the Fifth European Software Engineering Conference, ESEC’95, September 1995. [21] Jeff Magee and Jeff Kramer. Concurrency: State Models and Java Programs. Wiley, 1999. [22] Nenad Medvidovic, Peyman Oreizy, Jason E. Robbins, and Richard N. Taylor. Using object-oriented typing to support architectural design in the C2 style. In SIGSOFT’96: Proceedings of the Fourth ACM Symposium on the Foundations of Software Engineering. ACM Press, October 1996. [23] Nenad Medvidovic and Richard N. Taylor. Architecture description languages. In Software Engineering – ESEC/FSE’97, volume 1301 of Lecture Notes in Computer Science, Zurich, Switzerland, September 1997. Springer. [24] Daniel Le Metayer. Software architecture styles as graph grammars. In Proceedings of the Fourth ACM Symposium on the Foundations of Software Engineering. ACM SIGSOFT, October 1996. [25] Robert T. Monroe. Rapid Develpomentof Custom Software Design Environments. PhD thesis, Carnegie Mellon University, July 1999. 24 David Garlan [26] M. Moriconi, X. Qian, and R. Riemenschneider. Correct architecture refinement. IEEE Transactions on Software Engineering, Special Issue on Software Architecture, 21(4):356–372, April 1995. [27] P. Oriezy et al. An architecture-based approach to self-adaptive software. IEEE Intelligent Systems, 14(3):54–62, 1999. [28] S. Owre, J. M. Rushby, and N. Shankar. PVS: A prototype verification system. In Deepak Kapur, editor, 11th International Conference on Automated Deduction (CADE), volume 607 of Lecture Notes in Artificial Intelligence, pages 748–752. Springer-Verlag, June 1992. [29] Dewayne E. Perry and Alexander L. Wolf. Foundations for the study of software architecture. ACM SIGSOFT Software Engineering Notes, 17(4):40–52, October 1992. [30] Mary Shaw, Robert DeLine, Daniel V. Klein, Theodore L. Ross, David M. Young, and Gregory Zelesnik. Abstractions for software architecture and tools to support them. IEEE Transactions on Software Engineering, Special Issue on Software Architecture, 21(4):314–335, April 1995. [31] Mary Shaw and David Garlan. Formulations and formalisms in software architecture. In Jan van Leeuwen, editor, Computer Science Today: Recent Trends and Developments, Lecture Notes in Computer Science, Volume 1000. Springer-Verlag, 1995. [32] Mary Shaw and David Garlan. Software Architecture: Perspectives on an Emerging Discipline. Prentice Hall, 1996. [33] Bridget Spitznagel and David Garlan. Architecture-based performance analysis. In Tenth International Conference on Software Engineering and Knowledge Engineering (SEKE’98), San Francisco, CA, June 1998. [34] Richard N. Taylor, Nenad Medvidovic, Kenneth M. Anderson, Jr. E. James Whitehead, Jason E. Robbins, Kari A. Nies, Peyman Oreizy, and Deborah L. Dubrow. A component- and message-based architectural style for gui software. IEEE Transactions on Software Engineering, 22(6):390–406, June 1996. [35] Michel Wermelinger. Formal specification and analysis of dynamic reconfiguration of software architecture. In Proceedings of the 20th International Conference on Software Engineering, volume 2, pages 178–179. IEEE Computer Society Press, 1998.
© Copyright 2026 Paperzz