Chapter 3

SYNOPSIS: A Software Architecture Description Language

In this chapter, we begin the description of the principal end products of this research. Our overarching objective, argued for in the previous chapter, is to support a more efficient process for developing software applications out of existing parts. The process generates new applications by applying a series of computer-assisted transformations to descriptions of their architecture. The purpose of this chapter is to describe the first requirement for supporting the process: An architecture description language called SYNOPSIS. The language provides linguistic support for representing and transforming software architecture diagrams. It has two principal distinguishing features: First, it enables a clear separation of an application's core functional pieces from their interdependencies. Second, it supports entity specialization, a mechanism through which software architectures can be described at various levels of abstraction. The chapter begins by outlining the major design objectives of SYNOPSIS. It then gives an overview of the language, based on a simple example. The main body of the chapter is devoted to a detailed discussion of SYNOPSIS language elements, element transformations, and compatibility checking mechanism.

3.1 Language Design Objectives

The purpose of SYNOPSIS is to provide linguistic support for representing and transforming the entities of the component-based application development process that was outlined at the end of the previous chapter (see Figure 2-8). More specifically, support is needed in the following three areas:

3.1.1 Representation of Application Architectures

It should be possible to clearly separate the core functional pieces of an application from their application-specific patterns of interdependencies.

Chapter 2 presents our detailed argument for developing software system description notations with this property. SYNOPSIS satisfies this requirement by supporting two distinct language elements: activities, for representing core functional parts and dependencies, for representing interconnection relationships among activities.

It should be possible to separate the specification of system architecture from the implementation of the elements being structured.

Such a separation is desirable in order to be able to:

select or replace components for implementing activities independently of one another (their specification-level interdependencies do not change; different component implementations might influence the implementation of interaction protocols)

port an application to different configurations (specification-level relationships do not change; implementation of interaction protocols is the element most likely to change across configurations)

SYNOPSIS satisfies this requirement by representing implementation-level entities as optional attributes of activities and dependencies. Thus, activities can optionally be associated with a code-level component, such as a source code module, an executable program, or a network server. Dependencies can optionally be associated with a coordination process, representing a protocol for managing the relationship described by the dependency.

Section 3.3 describes SYNOPSIS language elements in detail.

3.1.2 Transformation of Application Architectures

It should be possible to connect the specification of system architecture and the implementation of the elements being structured through well-defined, structural transformations.

This requirement is instrumental to allowing the generation of executable applications directly from their architectural diagrams. SYNOPSIS supports the mechanism of entity specialization which allows generic application architectures to be transformed to executable systems by successive specialization of activities and dependencies contained in their decomposition.

Section 3.5 describes the mechanism of entity specialization.

3.1.3 Support for Compatibility Checking

It should be possible to specify and check compatibility restrictions and configuration constraints on the interconnection of application elements.

In a manner analogous to type checking in programming languages, it is desirable to be able to perform limited static checking of compatibility when connecting or transforming elements. Such controls facilitate the construction of correct architectures and help designers focus their attention to more complex issues. SYNOPSIS provides such a mechanism, based on matching element attribute values. The mechanism is described in Section 3.4.

3.2 Language Overview

This section introduces a simple example application that will serve as a guide to the various elements of SYNOPSIS. We will refer to this example again in Chapters 5 and 6, in order to illustrate how the system can integrate a set of heterogeneous software components into a smoothly running executable application.

The example system is a file viewer application that consists of three simpler subsystems:

_ a user interface that repeatedly asks users for code numbers of interest

_ a filename retrieval subsystem that receives user-supplied code numbers and retrieves matching filenames from a database

_ a file viewer subsystem that displays the contents of each retrieved file

Figure 3-1 depicts the architecture of the application, expressed as a SYNOPSIS diagram. Two distinct kinds of entities can be immediately distinguished in the diagram: Activities, drawn as rectangles, and dependencies, drawn as ovals.

Each of the three application subsystems corresponds to a different activity in the diagram. Activity Select Files is atomic, while activities Retrieve Filenames and View Files are composite, decomposing to patterns of simpler elements. The entire application, File Viewer, is also defined as a composite activity.

Dependencies encode relationships among the application's activities. Flow dependencies connect producers and consumers of data resources. In this application, they indicate that user-supplied code numbers must flow from the user interface to the filename retrieval subsystem. Likewise, retrieved filenames must flow from the filename retrieval to the file viewing subsystem. Prerequisite dependencies (labeled Persistent Prereq) indicate that the filename database must be opened (once) before any filename retrieval takes place, and that the file viewer subsystem must be initialized (once) before any file can be displayed.

Activities and dependencies are connected together via ports. Ports are an abstract mechanism for representing an element's needs for interaction with the rest of the system. For example, activity Retrieve Filename, has three ports: Two of them are data resource ports, encoding the fact that it needs to receive data resources of type Integer (code number), and that it produces data resources of type String (filename). The third is a Begin control port, encoding the fact that flow of control into the activity must follow the completion of activity Open DB.

All elements of the language (activities, dependencies, ports) may contain an arbitrary number of additional attributes. Attributes are name-value pairs that encode additional information about the element. They are also used to express compatibility restrictions that constraint the connection of elements.

Activity ports are connected to corresponding dependency ports using wires, drawn as line segments in the diagram. Every time a new connection is attempted, the system performs a compatibility check, based on unification of corresponding attribute values at the two ends of the connection.

The diagram of Figure 3-1 conveys the structure of the File Viewer application independent of the implementation of its elements. It is a generic (specification-level) view of the application. The goal of the process outlined in Section 2.4 is to be able to generate an executable system by applying a series of transformations to that diagram. From a language perspective, this requires support for implementation-level entities that implement the intended functionality of activities and dependencies.

SYNOPSIS supports such a set of entities, defined as attributes of their respective activities and dependencies. They are called software components, coordination processes and software connectors.




Figure 3-1: SYNOPSIS architectural description of a File Viewer application.

Software components are code-level software entities, such as source code modules, executable programs, user-interface functions, network services etc. SYNOPSIS provides a special notation for describing the properties of software components (see Section 3.3.1.2).

Our objective in this example is to construct the File Viewer application out of a set of existing, heterogeneous code-level components:

_ a C source module implementing the user interface subsystem

_ a Visual Basic module implementing the filename retrieval subsystem

_ a commercial text editor in executable form implementing the file viewing subsystem

Coordination processes manage dependencies. They represent implementations of interaction protocols. They are patterns of simpler activities and dependencies. Finally, software connectors represent low-level interconnection mechanisms, directly supported by the semantics of programming languages and operating systems. Examples include procedure calls, method invocations, local variables, etc.

3.3 Language Elements

3.3.1 Activities

Activities represent the main functional pieces of an application. They own a set of ports, through which they interconnect with the rest of the system. Interconnections among activities are explicitly represented as separate language elements, called dependencies. Activities must always connect to one another through dependencies. As a consequence, every activity port must be connected to a compatible dependency port.

Activities are defined as sets of attributes which describe their core function and their capabilities to interconnect with the rest of the system. The two most important activity attributes are:

An (optional) decomposition. Decompositions are patterns of simpler activities and dependencies which implement the functionality intended by the composite activity. The ability to define activity decompositions is the primary abstraction mechanism offered by SYNOPSIS.

An (optional) component description. Component descriptions connect SYNOPSIS activities with code-level components which implement their intended functionality. Examples of code-level components include source code modules, executable programs, network servers, etc.

Depending on the values of the above two attributes, activities are distinguished as follows (Figure 3-2):


Figure 3-2: A classification of SYNOPSIS activities.

Atomic or Composite. Atomic activities have no decomposition. Composite activities are associated with a decomposition into patterns of activities and dependencies.

Executable or Generic. SYNOPSIS allows the description of software systems at various levels of abstraction. This is manifested by allowing activities to be executable or generic. Executable activities are defined at a level precise enough to allow their translation into executable code. Activities are executable either if they are associated with a component description, or if they are composite and every element in their decomposition is executable. Activities which are not executable are called generic. To generate an executable implementation, all generic activities must be replaced by appropriate executable specializations.

Throughout this thesis, we will use the term primitive to refer to entities that are both atomic and executable.

3.3.1.1 Activity decompositions

Activity decompositions are patterns of activities and dependencies that implement the intended functionality of a composite activity, with which they are associated. Activity decompositions must obey the following structural restrictions (Figure 3-3):

All free ports of activities contained in the decomposition (i.e. ports not connected to other decomposition elements) must be connected to compatible ports of the composite activity. This simply states that the external points of interaction of a composite activity with the rest of the system are equal to the points of interaction of its decomposition, minus the interactions that are internal to the decomposition.

Dependencies contained in an activity decomposition can have no free ports. This states that, if a dependency is included in a decomposition, all of its ports must be connected to activities included in the same decomposition. Dependencies that span composite activities must be defined outside of any of them. This rule is an equivalent way of expressing the requirement that activities must always be connected to one another through intermediate dependencies.



Figure 3-3: Rules for creating activity decompositions.

3.3.1.2 Component descriptions

Activities describe the functional pieces of an application. These functional pieces are implemented either by decomposing them into equivalent patterns of simpler activities and dependencies, or by associating them with appropriate code-level software components. In order to properly integrate such software components into executable systems, our system needs some information about their interfaces, source and object files, and other related attributes. SYNOPSIS provides a special notation called Component Description Language (CDL) for describing the properties of software components associated with executable activities. CDL should be thought of as "syntactic sugar" that eventually translates into sets of SYNOPSIS attribute definitions. These definitions are stored in the corresponding executable activity.

SYNOPSIS component descriptions must include information about:

_ the component kind

_ the provided interface of the component

_ expected interfaces of other components

_ source and object files needed by the component

_ additional attributes specific to the component

The following paragraphs describe each part of the description in more detail:

Component kinds

Software components come in a variety of forms, such as source modules, executable programs, user-interface functions, etc. One of the main objectives of the entire project is to be able to combine together heterogeneous components of arbitrary form. SYNOPSIS provides support for defining arbitrary component kinds. This mechanism is described in detail in Chapter 5. From a CDL perspective, each component kind is represented by a different keyword. Table 3-1 describes the component kinds supported by the current prototype implementation of the system.
Component Type CDL KeywordDescription
Source procedureproc A source code procedure or equivalent sequential code block (subroutine, function, etc.)
Source modulemodule A source code module, consisting of one or more source code files and containing its own entry point (main program). Modules interact with the rest of the system through expected interfaces only.
Filterfilter A source code procedure that reads its inputs from and/or writes its outputs to sequential byte streams
Executableexec An executable program
DDE serverddes A DDE server embedded inside an executable program
OLE serveroles An OLE server embedded inside an executable program
Gui-Functiongui A function provided by the graphical user interface of some executable program, typically activated through a key sequence

Table 3-1: Component kinds supported by the current implementation of SYNTHESIS.

Provided component interface

Software components interact with other components by providing input-output interfaces. Depending on the component kind, such interfaces might correspond to procedure parameter lists, executable program command line invocation interfaces, user interface function activation key sequences, etc. When associating software components to executable activities, each element of the provided interface must be mapped to a corresponding atomic port of the activity.

Expected interfaces of other components

This part of the description reflects the fact that current-technology software components often make assumptions about the existence of other components or services in the system. For example, a source module might be calling an externally defined procedure with specified name and parameter interface. A Microsoft Windows executable program might assume the existence of a DDE server application with given specifications. Such expectations are documented in this section, using a syntax identical to the one used to define the component's provided interface. When associating software components to executable activities, each element of each expected interface must also be mapped to a corresponding atomic activity port.

Files needed by the component

Software components are usually pieces of code, residing in one or more source or object files. This section specifies the relevant set of files, to be eventually included in the final application.

Additional component attributes

For every kind of component, there is a set of required attributes that must be specified. Examples of such attributes include the programming language (for source components), the invocation pathname (for executable programs), the service and topic names (for a DDE server component), the activation key sequence (for user-interface services), etc. There are also optional attributes, such as restrictions on the target environment or host machine.

Figure 3-4 shows the CDL descriptions of components associated to the five executable activities of the File Viewer application that was introduced in Section 3.2.



Component "Select Files" IsA Procedure

Provides:

proc select_files();

Expects:

proc view_selected_files(in codenum:Integer);

Source Files:

\fviewer\select.c

Attributes:

Language = c

End Component

Component "Retrieve Filename" IsA Function

Provides:

func retrieve_filename(in codenum:Integer):String;

Source Files:

\fviewer\retrieve.bas

Attributes:

Language = vb

End Component

Component "Start Viewer" IsA Executable

Provides:

exec msword();

Attributes:

ExePath = \applic\msoffice\winword\winword.exe

End Component

Component "Open DB" IsA Procedure

Provides:

proc init_DB();

Source Files:

\fviewer\retrieve.bas

Attributes:

Language = vb

End Component

Component "View New File" IsA Gui-Function

Provides:

gui open_file(in filename:String);

Attributes:

guiWindow = "Microsoft Word"

guiKeys = "^O@1~"

End Component

Figure 3-4: Component Descriptions for the File Viewer example application (Figure 3-1).

Activity Select Files is associated with a C source code function, called select_files. This function contains an internal loop that repeatedly asks users for a code number. For each user-supplied code number, it calls function view_selected_files, to which it passes the user-supplied code number as an argument. Function view_selected_files is not part of this module and is expected to exist somewhere in the same executable. Its single parameter codenum is mapped to a corresponding producer port of activity Select Files. File select.c contains the code and data definitions for select_files and any other internal functions it may call.

Similarly, activities Open DB and Retrieve Filename are associated with Visual Basic procedures defined in file retrieve.bas.

Activity Start Viewer is associated with an executable program (Microsoft Word, a commercial text editor). Enactment of the activity corresponds, in this case, to invoking the program. The component's CDL provided interface describes the command line parameters for starting the program. In this case there are no command line parameters, other than the program pathname.

Finally, activity View New File is associated with a graphical-user-interface function offered by the text editor. In order to display a new file, the text editor normally expects to receive a key sequence in its main window, consisting of the character CTRL-O, followed by the required filename, followed by a newline character. Thus, this service expects one abstract data resource (the filename), embedded in a key sequence whose generic format is described by attribute guiKeys. In this example, guiKeys contains the characters ^O (=CTRL-O), followed by @1 (=the value of the first interface element, in this case the filename), followed by ~ (=newline). Finally, attribute guiWindow specifies the title of the editor's main window, where the activation key sequence must be sent.

3.3.2 Dependencies

Dependencies describe interconnection relationships and constraints among activities. Traditional programming languages do not support a distinct abstraction for representing such relationships and implicitly encode support for component interconnections inside their abstractions for components (see Chapter 2). In contrast, SYNOPSIS requires that all interconnections among activities are explicitly represented using dependencies. Like activities, dependencies own ports. Dependency ports must be connected to compatible activity ports.

Like activities, dependencies are defined as sets of attributes. The most important attributes are:

An (optional) decomposition into patterns of simpler dependencies that collectively specify the same relationship with the composite dependency.

An (optional) coordination process. Coordination processes are patterns of simpler dependencies and activities that describe a mechanism for managing the relationship or constraint implied by the dependency.

An (optional) association with a software connector. Connectors are low-level mechanisms for interconnecting software components that are directly supported by programming languages and operating systems. Examples include procedure calls, method invocations, shared memory, etc.


Figure 3-5: A classification of SYNOPSIS dependencies.

Depending on their values in the above attributes, dependencies are distinguished into the following categories (Figure 3-5):

Atomic or composite. Atomic dependencies have no decomposition. Composite dependencies are associated with patterns of simpler dependencies that specify the same relationships.

Managed or unmanaged. Managed dependencies have an associated coordination mechanism, specifying one way of managing the interconnection relationship they represent. Unmanaged dependencies have no associated coordination process. They simply specify the existence of a relationship and must be specialized by appropriate managed dependencies before code generation can take place.

Executable or generic. Executable dependencies are defined at a level specific enough to be translated into executable code. Dependencies are executable if one or more of the following conditions hold: (1) they are directly associated with a code-level connector, (2) they are composite and all elements of their decomposition are executable, (3) they are managed and all elements of their coordination process are executable.

SYNOPSIS allows the definition of arbitrary dependency types. However, one of our goals is to make the step of specifying and managing dependencies a routine one. For that reason, it is useful to define a standardized vocabulary of common dependency types that covers a large percentage of activity relationships encountered in software systems. Such a vocabulary will be described in Chapter 4. Figure 3-6 shows some examples of common dependencies.



Figure 3-6: Examples of common dependency types.

3.3.2.1 Dependency decompositions

Some dependencies can be equivalently described by patterns of simpler dependencies. Such patterns are called dependency decompositions and are defined as attributes of composite dependencies. Figure 3-7 shows some examples of composite dependencies and their decompositions.


Figure 3-7: Examples of dependency decompositions.

3.3.2.2 Coordination processes

Dependencies specify interconnection relationships which translate into needs for interaction among a set of activities. For example, a prerequisite dependency indicates a need to make sure that an activity does not take place before another activity has ended.

To generate executable systems, dependencies must be managed by inserting appropriate interaction or coordination mechanisms into the system. SYNOPSIS provides the abstraction of coordination process, defined as an attribute of dependencies, to represent and localize the definition of such interaction mechanisms.

Coordination processes are patterns of simpler dependencies and activities. In that sense, they are very similar to activity decompositions. Coordination processes must obey the following structural restrictions, which are the dual of the equivalent restrictions for activity decompositions (see Section 3.3.1.1):

All free ports of dependencies contained inside a coordination process (i.e. ports not connected to other elements of the coordination process) must be connected to compatible ports of the associated dependency. This simply states that the external points of interaction of a coordination process with the rest of the system are equal to the points of interaction of its elements, minus the interactions that are internal to the coordination process.

Activities contained inside a coordination process can have no free ports. This basically states that, if an activity is included in a coordination process, all of its ports must be connected to dependencies included in the same coordination process. Activities that span coordination processes must be defined outside of any of them. This rule is an equivalent way of expressing the requirement that dependencies must always be connected to one another through intermediate activities.

Figure 3-8 shows a SYNOPSIS definition of a coordination process that implements a pipe channel protocol (manages one-to-one flow dependencies). It is a pattern of three activities, and five lower-level dependencies.



Figure 3-8: A coordination process for managing flow dependencies.

3.3.2.3 Software connectors

Dependencies describe interconnection relationships among components. These relationships are managed either by coordination processes, described as patterns of simpler activities and dependencies, or by associating them with appropriate code-level software connectors. Connectors represent low-level software component interaction mechanisms built into the semantics of programming languages, or implicit in the semantics of operating system calls. Examples include:

_ design-time ordering of program statements in the same sequential code block (manages prerequisite dependencies)

_ local variable transfer (manages flow dependencies)

_ transport of data from one process to another implicit in the semantics of a UNIX pipe protocol

3.3.3 Ports

Ports encode abstract interfaces ("needs for interaction") of activities and coordination processes. All connections between SYNOPSIS language elements are done through ports.

As is the case with activities and dependencies, SYNOPSIS ports are distinguished into composite and atomic.

Composite ports act as abstract port specifications, describing a port's logical role in the system. They decompose into sets of simpler ports, which might themselves be composite or atomic.

Atomic ports directly map to an element of some implementation-level component interface (see Section 3.3.1.2).


Figure 3-9: SYNOPSIS built-in port specialization hierarchy

Figure 3-9 shows the SYNOPSIS built-in specialization hierarchy for ports. SYNOPSIS users are free to define new ports as specializations of the built-in types, or define completely new port types.

3.3.3.1 Atomic ports

The built-in port hierarchy implies that atomic ports are either input or output ports of data or control.

Atomic data ports are mapped to atomic interface elements of software components or executable coordination processes. The mapping is performed by setting attribute MapsTo at the port to a string indicating the respective interface element. Depending on the component kind, an atomic port might thus correspond to a source procedure parameter, a command line argument, a user-interface-function activation key sequence, an event detected or generated by a program, an object generated or used by an executable program, etc. All atomic data ports contain an attribute named Resource, which lists the data type expected to flow through the port.

Atomic control ports model the flow of control in and out of an activity or coordination process. Every activity has at least one pair of control ports modeling the beginning and end of execution of the activity. These ports are called the Begin and End ports. Complex activities might have additional control ports, modeling, for example, periodic calls to other activities that take place during the life of the original activity (Figure 3-10).
Component "Act A" IsA Procedure

Provides:

proc act_a();

End Component

Component "Act B" IsA Procedure

Provides:

proc act_b();

Expects:

proc service();

End Component



Figure 3-10: Begin, End, and additional control ports.

3.3.3.2 Composite ports

Sets of atomic ports can be optionally grouped inside composite ports. Our built-in port hierarchy contains only two types of composite ports: producer and consumer ports, specifying the production and consumption of abstract resources.

Designers might want to use composite ports for two reasons:

to indicate sets of logically related interface elements in a given implementation, and

to indicate generic needs of interaction when designing generic entities. Different specializations of a given generic entity might contain different decompositions of the same composite port, corresponding to the variety of interfaces by which a given abstract need for interaction can be implemented.

The rest of the section describes an example of the use of composite ports.

We are designing an application which processes the contents of an input file. One of the activities in this application will make the contents of that file available to the rest of the system. Initially, we do not know the exact implementation and interface of the component that will implement that activity. We, therefore, specify a generic activity called Generate Input, which contains a generic producer port, through which the contents of the input file will be made available to the rest of the system (Figure 3-11(a)). In the application diagram, that producer port will be connected to dependency ports connecting it to the consumer activities.

There are at least two ways to implement activity Generate Input. Figure 3-11(b) shows a filter implementation. The activity is associated to a component that writes its output to an externally opened sequential byte stream. The same byte stream must be read by consumer activities to retrieve the contents of the input file. In this case, the composite producer port decomposes to a data input port, corresponding to the file descriptor of the sequential byte stream that must be passed to the filter. It is interesting to note that, in this case, a composite producer (i.e. output) port, decomposes into an atomic data input port.

Figure 3-11(c) shows another way to implement Generate Input. For each line of the input file, the second implementation calls a procedure, which it expects to have been defined externally. It passes the current input line as a string parameter to the procedure. In this case, the composite input port decomposes into a data output port, corresponding to the string parameter of the procedure called by the component.

By default, every composite activity port includes the Begin and End ports of its associated activity in its decomposition. Activity ports are directly connected to coordination process ports. Coordination processes often need to control what happens before or after the execution of an activity. This requires them to have access to the Begin and End ports of an activity. In actual practice, this requirement is frequent enough that we decided it to include Begin and End ports in the decomposition of every composite activity port by default.


Figure 3-11: (a) A generic activity containing a generic producer port.

(b) One executable specialization of the previous activity and its corresponding generic port decomposition into atomic ports.

(c) Another executable specialization of the same activity, which has a different decomposition of the generic port.

3.3.4 Resources

Most interactions among software components center around the flow and sharing of various kinds of resources. In most cases, resources are dynamically produced during run-time, as a result of execution of activities. Such dynamically produced resources are implicitly modeled in SYNOPSIS diagrams through producer ports. As mentioned in the previous section, ports contain special attributes which describe the data type, or other properties, of the resources that flow through them.

In many software applications, however, there exist some resources that have been produced outside of the scope of the application. Examples include hardware resources, such as a printer, and resources produced by other applications, such as a database.

SYNOPSIS models such preexisting resources using a special resource language entity. Like activities, resource entities own ports, through which they are connected to the rest of the system. Atomic activity ports, are associated with interface elements of software components. In contrast, atomic resource ports are bound to constant data values, which usually correspond to resource identifiers.

Figure 3-12 depicts an example of the use of resource entities in SYNOPSIS diagrams. Resource entity Database represents a preexisting database file which is being shared by two activities in this application. Database has a single port, which is bound to the filename of the database file. This filename must be made known to the two user activities (through a Flow dependency), in order for them to be able to access the database.



Figure 3-12: Example of resource entity use in SYNOPSIS diagrams.

3.3.5 Attributes

Attributes are name-value pairs that specify additional information about SYNOPSIS language entities. Attribute values can be scalar, lists, or pointers to other SYNOPSIS entities. Although SYNOPSIS supports the definition of arbitrary attributes, the system is using some conventions about required attributes in each entity type. Some of those conventions have been collected in Table 3-2. As more experience is gained from using the system, more such useful conventions are expected to emerge.

In the current implementation attributes play an important role:

_ in determining compatibility of an element with its neighbors in the system

_ in integrating components and coordination processes into sets of executable modules

The following additional features of the attribute mechanism are especially designed to support the compatibility checking process:

Entity type Required

Attributes

Description
All activitiesEnvironment execution environment(s) for which activity is compatible
Activities associated to source module components Language

Executable

source programming language of module

name of executable where module is to be integrated

Activities associated with executable program components ExePathpathname of executable program
Activities associated with DDE server components Service

Topic

Server DDE service name

Server DDE topic name

Activities associated with graphical user interface function components guiKeys

guiWindow

Format string of activation key sequence

Name of window where activation key sequence should be sent

All atomic portsMapsTo

Resource

pointer to interface element corresponding to port

type of data flowing through port

Table 3-2: Partial list of required attributes for each SYNOPSIS entity type.

Attribute Inheritance

As will be described in Section 3.5, all SYNOPSIS language elements inherit the decomposition and all attributes from their specialization parents. In addition to that, SYNOPSIS language elements also inherit all attributes defined at their decomposition parents. This feature enables attributes that are shared by all members of a composite element to be defined only once at the decomposition parent level. For example, in Figure 3-13, all activities and dependencies contained inside UNIX Pipe Transfer inherit the attribute definition Environment = UNIX, which denotes that they are compatible with the UNIX environment.

Since ports owned by an element are considered to be parts of its decomposition, ports always inherit all attributes defined at their owner.



Figure 3-13: Attribute definitions are inherited by all decomposition children.

Attribute Variables

In addition to normal values, attributes can be assigned to attribute variables. Attribute variables are strings that begin with "?". For example:

Resource = ?res

Procedure = ?proc

Attribute variables need not be declared. They are automatically created the first time they are referenced.

Unique variables are special attribute variables whose names begin with the string "?unq" (e.g. ?unq-var, ?unq1, etc.). They have the special requirement that their values cannot be equal to the value of any other variable in the same scope. They are used to express inequality constraints (see Section 3.4.1).

Attribute variables are local to each owner element (activity, dependency, resource) but are shared among an owner element and all its ports. For example, in Figure 3-14, both references to variable ?res at ports of Activity A refer to the same variable. Setting the variable at one of the ports will affect both attribute definitions. Likewise, both references to variable ?res at ports of Activity B refer to the same variable. However, references across activities refer to different variables. For example, setting the variable ?res at Activity A will not affect the value of variable ?res at Activity B.



Figure 3-14: Scope rules of attribute variables.

3.3.6 Wires

Wires, drawn as simple line segments, indicate that a legal connection has been established between two ports. Wires established between composite ports imply a set of legal connections between each of their corresponding subports.

There are two types of wires in SYNOPSIS diagrams.

Internal wires connect free ports of decomposition or coordination process elements to compatible ports of their associated composite elements. For example, in Figure 3_3 two internal wires connect the two free ports of atomic activity Retrieve Filename to compatible ports of composite activity Retrieve Filenames. Likewise, in Figure 3-8 internal wires connect the three Flow dependencies contained in the decomposition of One-To-Many Flow to compatible ports of the composite dependency. Establishment of a legal internal wire requires that the decomposition or coordination process port (the internal port) is equal to or a specialization of the composite element port (the external port) to which it is being connected.

External wires connect activity ports to dependency ports. It is illegal to connect an activity port directly to another activity port, or a dependency port directly to another dependency port. Furthermore, the port at the activity side must be equal to or a specialization of the port at the dependency side. This restriction reflects the coordination perspective on software architecture adopted by SYNOPSIS, whereby software systems are represented as sets of activities interconnected through explicitly represented relationships (dependencies).

3.4 Checking Element Compatibility

SYNOPSIS language elements have several semantic restrictions and constraints on their ability to connect to other elements. Some of these restrictions are encoded in the syntax of the language. For example, activity ports can only be connected to dependency ports (and vice versa). Furthermore, dependency ports can only connect to activity ports that are equal to or a specialization of them. Such restrictions are described in Section 3.3.6. Other restrictions are specific to the attributes of code-level elements (components and connectors) associated with executable activities and dependencies. Examples of such restrictions include:

An activity associated with a source code procedure returning an integer value can only be connected to other activities through dependencies associated with coordination processes capable of transporting integer values.

_ Flow dependencies managed by local variable connectors can only connect activities whose associated components can be packaged inside the same sequential code block.

_ Flow dependencies managed by DDE transfer coordination processes can only connect activities whose associated components will be packaged in different executables within the same Microsoft Windows-based host.

We would like to be able to encode such restrictions and constraints, so that a number of compatibility tests can be performed automatically by the language. Hence, a problem similar to type checking in a programming language arises in SYNOPSIS whenever two elements are connected together. Since all SYNOPSIS connections are between ports, it is desirable to be able to perform compatibility checks at the port level.

SYNOPSIS bases its compatibility checking on attributes. Attributes are used to specify compatibility restrictions. Before establishing a connection between two ports, SYNOPSIS performs a port matching algorithm that determines port compatibility.

Figure 3-15 describes the algorithm. The algorithm compares the values of all attributes that exist at both sides of the attempted connection. If both attributes have a fixed value then the two values must match. If either attribute is assigned to a variable, a process similar to unification takes place. If at least one attribute fails to match, the connection is considered illegal.


Check_Compatibility( aport, dport )
-- aport = activity port
-- dport = dependency port
-- Returns: SUCCESS if ports can be legally connected, FAILURE otherwise
-- Uses: Match_Values( va, vd )

If one port is composite and the other is atomic then return FAILURE.

If both ports are composite then recursively match subports.

If both ports are atomic then
If aport is same as or a specialization of dport then
For each attribute defined at both ports (including inherited attributes)
If both ends have a value then call Match_Values
else If one end refers to a variable then
If variable has a value then call Match_Values
else Set variable and its equivalence class to value at other end

Return SUCCESS.
else If both ends refer to variables
If one or both variables have values then do as above.
If no variable has a value then
Unify both variables into an equivalence class
Return SUCCESS.
else return FAILURE.

Match_Values( va, vd )
-- va = value of attribute at activity side
-- vd = value of attribute at dependency side
-- Returns: SUCCESS if values match, FAILURE otherwise.

If values are identical then return SUCCESS.

If values are pointers to language elements then
If va is a specialization of vd
then return SUCCESS.
Return FAILURE

Figure 3-15: Port compatibility checking algorithm.

3.4.1 Using Attributes to Encode Constraints

The semantics and scope rules of attributes and variables (see Section 3.3.5) permit a number of compatibility constraints to be encoded into attributes. The following paragraphs give some examples of how some of the most useful kinds of constraints can be encoded using the attributes mechanism.

"An attribute must have a specific value at all neighbor elements"

This is encoded by defining the attribute with the specified value at all ports where this condition is required. If the condition is required at all ports of an owner element, the attribute need only be defined once at the owner element.

Example: A dependency managed by a coordination process encoding a UNIX pipe protocol can only be used to connect components that will run in UNIX environments. Designers can encode this constraint by defining an attribute Environment set to UNIX at the top level of the process (Figure 3-13). This attribute is inherited by all ports. In order to pass the compatibility check, all connected activities must contain the attribute set to the same value.

"An attribute must have the same (unspecified) value at all neighbor elements"

This can be encoded by setting the attribute to the same variable name at all ports where this condition is required. The unification process will only succeed if the equivalent attribute at the other end of all connections has the same value.

Example: Local variable transfer is the simplest coordination process for managing flow dependencies. However, it can only be used if all its members (a) produce and use the same data type, and (b) can be packaged into the same procedure. The latter requirement implies that they also have to be written in the same language and be packaged into the same executable program. All these constraints can be encoded into the ports of the associated dependency as shown in Figure 3-16.


Figure 3-16: Activities connected to both endpoints of this dependency must have identical values for the specified attributes.

"An attribute must have a different value at each neighbor element"

This can be encoded by setting the attribute to a different unique variable (see Section 3.3.5) at each port where this condition is required. Since each unique variable cannot be unified to a value given to any other variable, unification will only succeed if the attribute has a different value at each neighbor element.

Example: Dynamic Data Exchange (DDE) is one protocol for transferring data among different executable programs in the Microsoft Windows operating system. It does not work properly for transferring data within the same executable. Thus, it can only connect components that will be integrated into different executables. This constraint can be encoded into the ports of the associated dependency as shown in Figure 3-17.


Figure 3-17 : Activities connected at the endpoints of a dependency managed by a DDE Transfer coordination process must be packaged into different executables.

3.4.2 The Dual Purpose of Attribute Matching

Attribute unification simultaneously checks constraint compliance and creates equivalence classes of attribute values across a number of elements. For example, an attempt to connect two activities through a dependency managed by a local variable transfer coordination process (Figure 3-16), not only checks whether the two endpoint activities can be packaged together into the same procedure, but also unifies attribute Procedure at both of them to the same equivalence class. This will help the code generator determine that the two activities should be packaged into the same procedure.

Attribute unification is therefore one of the mechanisms used by the system in order to integrate activities and dependencies into sets of executable modules. This aspect of the system will be described in more detail in Chapter 5.

3.5 Entity Specialization

Object-oriented languages provide the mechanism of inheritance to facilitate the incremental generation of new objects as specializations of existing ones, and also to help organize and relate similar object classes. SYNOPSIS provides an analogous mechanism called entity specialization. Specialization applies to all the elements of the language, and allows new activities, dependencies, ports and resources to be created as special cases of existing ones. Specialized entities inherit the decomposition and other attributes of their parents. They can differentiate themselves from their specialization parents by modifying their structure and attributes using the operations described below. Entity specialization is based on the mechanism of process specialization that was first introduced by the Process Handbook project [Malone93, Dellarocas94].

Figure 3-18: A hierarchy of increasingly specialized coordination processes for managing prerequisite dependencies.

The mechanism of entity specialization enables the creation of specialization hierarchies for activities, dependencies, ports, and coordination processes. Such hierarchies are analogous to the class hierarchies of object-oriented systems. In specialization hierarchies, generic designs form the roots of specialization trees, consisting of increasingly specialized, but related designs. The leafs of specialization trees usually represent design elements that are specific enough to be translated into executable code (Figure 3-18). Specialization hierarchies have proven to be an excellent way to structure repositories of increasingly specialized design elements, such as the vocabulary of common dependency types we shall introduce in Chapter 4.

Apart from enabling the organization of related design elements in concise hierarchies, entity specialization also encourages application development by incremental refinement of generic application architectures. Designers can initially specify their applications using generic activities and dependencies. Such generic application architectures can then be iteratively refined by replacing their generic elements with increasingly specialized versions, until all elements of the architecture are executable. The existence of repositories of increasingly specialized design elements can reduce the specialization steps to a routine selection of appropriate repository elements.

3.5.1 Creating New Elements

In a way analogous to object-oriented class creation, all SYNOPSIS elements are created as specializations of other elements. New elements inherit the decomposition, coordination process, and all other attributes defined in their specialization parent. They can differentiate themselves from their parents by applying any number of the following transformations to their attributes (see [Wyner95b]):

_ Add or delete a decomposition or coordination process element

_ Replace a decomposition or coordination process element with one of its specializations

_ Add or delete a connection

_ Add or delete an attribute

_ Modify the value of an attribute

Figure 3-19 shows an example of how an executable dependency can be defined as a special case of a generic one by inheriting the coordination process of its parent and then replacing all generic activities and dependencies contained therein with executable specializations. Figure 3-19 (a) depicts a generic coordination process for managing flow dependencies using pipes. The process describes the essence of a pipe protocol independently of the particular system calls that are used to implement it in a given environment. It is generic because it contains three generic activities and five unmanaged dependencies. Figure 3-19 (b) depicts an executable specialization of the pipe transfer protocol, specific for the UNIX operating system. The specialization inherited the design elements of the original protocol and replaced generic activities and unmanaged dependencies with executable specializations, appropriate for the UNIX operating system.

3.5.2 Specializing Activities

This operation allows incremental refinement of architectural diagrams. Designers can specify SYNOPSIS architectures in very generic terms and then incrementally specialize their elements until an executable design has been reached. Such a process is supported by the ability to maintain hierarchies of increasingly specialized activities, encoding families of alternative designs for specific parts of software architectures.

From a linguistic perspective, an activity can only replaced with a specialization if the new specialization is able to structurally replace the original activity and legally connect to all neighbor elements connected to it. If the specialization is composite, the same requirements apply for its decomposition as well. The requirements translate to the following port compatibility requirements:

_ For each connected port of the original activity, there must be an equivalent port of the specialization.

_ Each port of the specialization must be compatible with the dependency port to which the corresponding port of the original activity was connected.






Figure 3-19: (a) A generic coordination process that manages flow dependencies using pipes.

(b) An executable specialization of the previous process.

3.5.3 Specializing Dependencies

Generic dependencies are descriptions of needs of interaction among software activities. They are useful for constructing configuration-independent architectural descriptions of software systems that clearly show why activities need to interact with one another. In order to derive executable implementations from such abstract descriptions, designers can follow two alternative paths:

If the generic dependency has a decomposition, replace all elements of the decomposition with executable specializations.

Replace the generic dependency with a managed specialization. Managed dependencies are associated with coordination processes. Coordination processes represent interaction protocols. From a linguistic perspective, a dependency can only replaced with a specialization if the new specialization is able to structurally replace the original dependency and legally connect to all neighbor elements connected to it. If the specialization is managed, the same requirements apply for its coordination process as well. The requirements translate to port compatibility requirements similar to the ones for specializing activities:

- For each connected port of the original dependency, there must be an equivalent port of the specialization.

- Each port of the specialization must be compatible with the activity port to which the corresponding port of the original dependency was connected.

3.6 The Way Ahead

SYNOPSIS provides the abstractions needed in order to describe application designs as sets of activities connected by dependencies. It also provides the mechanism of entity specialization that enables generic application architectures to be transformed to executable designs by:

replacing generic activities with executable specializations

replacing generic dependencies with managed specializations

integrating activities and dependencies into executable modules

The first transformation requires locating appropriate code-level components that implement activity functional requirements. We consider this problem the responsibility of the designer and have argued in Section 1.1 that it is being made simpler by today's technologies.

Chapter 4 will present a vocabulary of common dependency types and a design space of associated coordination processes that aims to reduce the second transformation to a routine design step, capable of being assisted, or even automated, by computer.

Finally, Chapter 5 will discuss the problems and solutions of automating the third transformation.


Continue on to Chapter 4