Vocabulary types and design rules provide mechanisms for capturing and encapsulating design expertise in the form of design vocabulary and constraints. Although individual types and design rules can be useful by themselves, expertise of this sort tends to be more useful when packaged as part of a coherent collection of related vocabulary and constraints. Acme’s architectural style construct provides the ability to aggregate and package related vocabulary and constraints.
An Acme architectural style specification consists of a set of vocabulary type definitions, a set of design rules, a set of design analyses, and a set of minimal required structure. Any or all of these sets may be empty. A style is fundamentally a system type that also defines a design space. Styles obey all of the rules and semantics of types presented thus far, with some additional syntax and semantics to support the style’s design space function.
The informal syntax for defining a style is:
Family <style-name> = { <style-element>* } ;
or
Family <style-name> extends <super-style-name>+
with { <style-element>* } ;
<style-element> ::= <Sequence of: required structure and values
| required properties
| explicit invariants
| explicit heuristics
| design analyses
| type definitions >
The syntax and semantics for declaring individual type specifications, design rules, and design analyses have been described earlier in the chapter. In its role as a design space, a style is a named collection (or a package) of such constructs. In its role as a system type, a style constrains the design of systems defined in that style.
System instances may make use of the design expertise packaged in a style by declaring that the system satisfies a style. The syntax for declaring that a system instance satisfies a style is:
System sample-system : sample-style = { <system-decl-body> } ;
When a system instance declares that it is designed in a specific style the names of all of the types and design analyses declared in that style are visible within the system instance. Further, all of the design rules contained in the style definition must hold over the system instance. That is, the design rules in the style definition take effect in the scope of the system instance, binding the concrete elements in the system instance to the appropriate abstract design rules of the style.
Declaring that a system is designed in a specific style indicates that the constraints specified by that style in the form of design rules must be maintained in the system instance. Failure to satisfy these constraints constitutes a type error.
The set of type specifications given in a style declaration provide a set of vocabulary types that can be used within a system specification done in that style. The system definition is not, however, limited to using only the types provided by the style (unless there is a design rule that explicitly limits the types of vocabulary that can be used). Design elements within the system instance that claim to satisfy a type defined in the style must, however, satisfy the type predicate given in the style definition.
Name visibility within a style. In order to insure that styles can be used as independent, modular packages, abstract design rules and types defined within a style have limited visibility to names defined outside of the scope of the style definition. Type definitions within a style may only be subclasses of types defined in a superstyle. Likewise, ports and roles defined within a component or connector type definition may only claim to satisfy types defined within the style itself or one of the style’s superstyles.
A design rule defined in a style may refer to design analyses defined within that style, design rules defined in an explicitly included library, the types defined within that style, and, of course, all primitive Acme predicates.
A style can extend an existing style to make use of the types and design rules defined in the existing style. The following example illustrates such an extension:
Family super = { ... };
Family sub extends super with {
Component type new-component = { ... };
Invariant forall x in self.components · foo(x));
};
This example creates a new style called sub that extends an existing style called super. The new style sub consists of the union of the types, design rules, design analyses, and structure defined in both the style “super” and those defined directly in the definition of style “sub.” The new style “sub” is a substyle of the style “super.” Because the substyling operation only allows additional types, design rules, and structure to be added to a style, any system that satisfies the constraints of sub will also satisfy the constraints of super.
A substyle may not redefine types or design rules named in the superstyle. It may, however, create new types that extend the types defined in a superstyle.
Multiple inheritance is supported in creating substyles. To create a substyle of multiple superstyles, a style definer simply adds multiple superstyles to the “extends with” statement, as the example below indicates.
Family sub extends super-1, super-2, super-3 with {
Component type new-component = { ... };
Invariant forall x in self.components | foo(x));
};
The semantics for using multiple superstyles are effectively the same as using a single superstyle. The new style “sub” will consist of the union of the types, abstract design rules, and binding statements defined in all of its “style-n” superstyles and those defined directly in the definition of style “sub.”
A system can declare that it satisfies multiple styles. In specifying that it satisfies multiple styles it also claims to satisfy the constraints of all of the styles used. The syntax for declaring that a system uses (satisfies) multiple styles is:
System sample-sys : style-1, style-2, ..., style-n = {...} ;
The semantics of a such a declaration say that the vocabulary of styles 1 through n are all available for use in the system, design rules from styles 1 to n are bound to sample-sys. As a result, sample-sys needs to satisfy all of the design rules of each style. The style constraints of system sample-sys are the unification of the vocabulary and quantified design rules of styles 1 to n.
The “new” operator defined earlier in this chapter for creating minimal instances of simple element types can also be used with styles to create systems with the minimal required structure to satisfy the style specification. As with simple element types, the “extended with” construct can be used to extend the minimal structure provided by “new” and customize the created system. The basic syntax for creating a new minimal instance of a style follows:
System <sys-name> : <style-name> = new <style-name> [ extended with {…} ] ;
The semantics for using the new operator with systems are the same as the semantics for using new with simple element types.
Using multiple styles in a single system can cause various kinds of conflicts. The two primary kinds of conflicts are name conflicts (for vocabulary and design rules) and conceptual conflicts where the styles are fundamentally incompatible.
Naming conflicts are fairly easy to deal with. The use of an ambiguous name within a system specification is an error. Ambiguous naming can be avoided by qualifying types and design rules specified within style definitions with the name of the desired style from which the type or design rule should be used. For example:
Family generic-cs = { ... Component Type client = {...} ... };
Family special-cs = { ... Component Type client = {...} ... };
System sample : generic-cs, special-cs = {
Component generic-client : generic-cs.client = {...};
Component special-client : special-cs.client = {...};
Invariant special-client.no-peers(...);
}
In this example, the client type is defined in both the generic-cs style and the special-cs style. Each of the *.client type components in the system sample explicitly specify which of the client types (special-cs.client or generic-cs.client) they claim to satisfy. The components in system sample cannot simply claim to be of type client, as the client type is provided by multiple styles used by the system, making the identifier “client” ambiguous. Type and design rule names that appear in only one style do not need to be qualified in a system declaration. Qualification with a style name is required only where the lack of a qualifying identifier leads to ambiguity. References from within a system specification to abstract design rules that are provided by styles follow the same set of disambiguating and qualifying rules as those used for disambiguating types defined by multiple styles.
Style designers do not need to explicitly qualify references to types or design analyses that are defined within the same style specification. All references to types or abstract design rules from within a style definition are implicitly qualified and, in fact, only able to, refer to types and abstract design rules of the same style. Consider the following (revised) example:
Family generic-cs = { ...
Component Type client = {...}
Component Type server = {...}
...
};
Family special-cs = { ...
Component Type client = {...}
Component Type server = {...}
Design Analysis only-cs-conns(c : client, s:server) : boolean = {...};
Invariant Forall
c,s in {select self.Components |
SatisfiesType(c, Client) and
SatisfiesType(s, Server) } | only-cs-conns(c,s);
}
System sample : generic-cs, special-cs = { ... }
In this example, the design rules and the types have been automatically (and implicitly) qualified to refer to their “native” style when evaluated in the context of the system instance.
Fundamental conceptual mismatch. The second important type of conflict that can occur when using multiple styles within a single system are fundamental conceptual mismatches. These conflicts occur because the styles being used are fundamentally incompatible with each other. An example of such a conflict is a system that merges a pipe-filter style, which requires that all components are filters and all connectors are pipes, with a client-server style that requires all components to be clients or servers and all connectors to be HTTP streams. Unless the required types are (accidentally) compatible with each other (e.g. instances of Filters satisfy the constraints of Client) it is unlikely that any non-empty system instances can be created that satisfy the constraints of both styles.
It is up to Acme users to detect and avoid such conceptual conflicts. Using multiple styles in a single system instance expands the vocabulary available for use in that system but generally constrains the design of the system further by introducing additional design constraints. As the previous example indicates, it is possible to overly constrain a design by using multiple styles. Tools can be developed to detect obvious style incompatibilities, but they will not eliminate the need to be careful when using multiple styles for a single system instance.