We start by considering the C&C viewtype in its most general form. In Chapter 4, we identify a few common C&C styles. Table 3.1 summarizes the elements, relations, and properties that can appear in the C&C viewtype. As we describe the constituents of a C&C viewtype, we also list guidelines about effective use of them in documenting software architecture.
|Properties of elements||Component.
|Topology||The C&C viewtype has no inherent topological constraints.|
The elements of a C&C viewtype are components and connectors. Each element in a C&C view of a system has a runtime manifestation, consuming execution resources and contributing to the execution behavior of that system. The relations of a C&C view associate components with connectors to form a graph that represents a runtime system configuration.
These runtime entities are instances of component and connector types. The available types are either defined by choosing a specific architectural style that prescribes a set of C&C building blocks (see Chapter 4), or they may be custom defined. In either case, the types are chosen because of significant commonality among several components or connectors in the architecture. Defining or using a set of component-and-connector types provides a means for capturing this commonality, provides a specialized design vocabulary targeted to specific domains, and introduces constraints on how that vocabulary is used.
Each component in a C&C view has a name. The name should indicate the intended function of the component. More important, the name allows you to relate the graphical element with supporting documentation.
Components are the principal computational elements and data stores that execute in a system.
Each component in a C&C view has a type. Examples of generic component types are clients, servers, filters, objects, and databases. More domain-specific component types might be a controller component type, used in a process control architecture, or a sensor component type, used in some avionics application. A component type is defined in terms of its general computational nature and its form. For example, a component having the type filter transforms data that is received on its input channel interfaces and transmits the result on its output channel interfaces. Similarly, a component having the type server must have an interface providing a set of service calls that clients may invoke and have as its main computational role to service those requests. A component's type description includes the number and kinds of interfaces it supports and required properties.
The relationship between architectural component types and their instances is similar but not identical to that between classes and instances in an object-oriented world. A component instance might define additional ports not required by its type or associate an implementation in the form of additional structure that is not part of the instance's definition.
The set of component types that contribute to a C&C view should be explicitly enumerated and defined. This may be done in a style guide for the style that you're using, if one exists. Or it may be done by defining the type in the properties of the component. For instance, the intended semantics of component types, such as filter or server, would need to be defined.
In some cases, the set of types used in a C&C view is provided by using a particular C&C architectural style. For example, a C&C view defined in the pipe-and-filter style will use pipe connector types and filter component types, as prescribed by that style.
Although components have types, a C&C view contains component instances; that is, no component types should appear in the view itself. A C&C view may have many components of the same type. For example, a view may have many instances of the same server type. If the designer does not want to commit to a particular number of a replicated component or if that number changes dynamically as a system operates, it is possible to indicate a variable number of a component type. This can be done in a number of ways. However, the meaning of any replication should be clearly documented. In particular, the documentation should specify whether the variability is resolved at design time, system configuration/deployment time, or runtime.
Components have interfaces. Given the runtime nature of C&C views, an interface is a specific point of potential interaction of a component with its environment. Component interfaces are referred to as ports to emphasize their runtime nature and to distinguish them from the interfaces of other kinds of architectural design elements, such as classes.
A component's ports should be explicitly documented. When documenting a component in a C&C view, it is important to be clear about the number and type of the component's ports. Note the use of plural: A component may have many ports of the same or different types. For example, the database in Figure 3.1 has two server-oriented ports and an administrative port.
Components may represent a complex execution structure, which in turn may be described as a collection of components and connectors. This component decomposition refinement may be represented in a style different from that in which the component is described.
Connectors are the other kind of element in a C&C viewtype. Simple kinds of connectors are procedure calls between two objects or between a client and a server, asynchronous messages, event multicast among components that communicate with one another by using publish-subscribe, and pipes that represent asynchronous, order-preserving data streams. But connectors often represent much more complex forms of interaction, such as a transaction-oriented communication channel between a database server and a client. These more complex forms of interaction may in turn be decomposed into collections of components and connectors that typically describe the runtime infrastructure that makes the more abstract form of interaction possible. For example, a decomposition of the failover client-server connector of Figure 6.1 would probably include components that are responsible for buffering client requests, determining when a server has gone, and rerouting requests.
A connector is a runtime pathway of interaction between two or more components.
As with components, each connector in a C&C view should have a type, which defines the nature of the interaction supported by the connector. The type also makes clear what form the connector can take, such as how many components can be involved in its interaction, the number and kinds of interfaces it supports, and required properties. Often, the interaction represented by a connector is best described as a protocol. A protocol describes what patterns of events or actions can take place for an interaction.
The set of connector types that contribute to a particular C&C view should, like component types, be explained by referring to the appropriate style guide that enumerates and defines them.
Part of the description of a connector type should be a characterization of the number and kinds of roles that instances of that type can have. A connector's roles can be thought of as interfaces of the connector, insofar as they define the ways in which the connector may be used by components to carry out interactions. For example, a client-server connector might have invokes-services and provides-services roles. A pipe might have writer and reader roles. A publish-subscribe connector might have many publisher and subscriber roles. A role typically defines the expectations of a participant in the interaction. For example, an invokes-services role might require that the service invoker initialize the connection before issuing any service requests.
The relation of the C&C viewtype is attachment. An attachment relation indicates which connectors are attached to which components, thereby defining a system as a graph of components and connectors. Formally, this is done by associating component ports with connector roles: A component port, p, is attached to a connector role, r, if the component interacts over the connector, using the interface described by p and conforming to the expectations described by r.
Use the following guidelines when defining a graph of components and connectors using attachments:
To illustrate what not to do, Figure 3.2 presents an example of a poorly documented C&C view.
Figure 3.2. A poorly documented C&C view. There is no key; it portrays an API as a component; it uses different shapes for the same types of element; it uses the same shape for different types of elements; it confuses the context with the system to be built; its use of arrows is not explained; it has no explicit interface points.
A particular element may have various kinds of associated properties, including the name and type of the element. Other properties are possible and should be chosen to support the usage intended for the particular component-and-connector view. For example, different properties might be chosen depending on whether the view is to be used as a basis for construction, analysis, or communication. Following are some examples of typical properties and their uses:
The ports and roles also might have important properties associated with their use, in which case these should be explicitly stated.
Are Connectors Necessary?
We argue that connectors should be first-class design elements for documenting execution-based views: Connectors can represent complex abstractions; they have types and interfaces, or roles; and they require detailed semantic documentation. But couldn't one simply use a mediating component for a complex connector? For example, in the following diagram, the complex connector Connector-1 gets replaced by the component Component-1:
In other words, are connectors needed? The answer is emphatically yes! Connectors are needed. Here's why.
First, connectors are rarely realizable as a single mediating component. Although most connector mechanisms do involve runtime infrastructure that carries out the communication, that is not the only thing involved. In addition, a connector implementation requires initialization and finalization code; special treatment in the components that use the connector, such as using certain kinds of libraries; global operating system settings, such as registry entries; and others.
Second, connector abstractions support rich reasoning. For example, reasoning about a data flow system is greatly enhanced if the connectors are pipes rather than procedure calls or another mechanism, because well-known calculi are available for analyzing data flow graphs. Additionally, allowing complex connectors provides a single home where one can talk about the semantics. For example, in the preceding figure, I could attach a single description of the protocol of interaction to the complex connector. In contrast, the lower model would require me to combine the descriptions of two connectors and a component to figure out what is going on.
Third, using connectors more clearly indicates the architect's intent. When components are used to represent complex connectors, it is often no longer clear which components in a diagram are essential to the application-specific computation and which are part of the mediating infrastructure.
Fourth, we need connector abstractions because avoidance of complex connectors can significantly clutter an architectural model with unnecessary detail. Few would argue that the lower of the two diagrams is easier to understand. Magnify this many times in a more complex diagram, and it becomes obvious that clarity is served by using connectors to encapsulate details of interaction.
Fifth, shifting the description as in the preceding diagram begs the question, as the new depiction also has connectors. So we haven't eliminated them.
"Oh, but these are much simpler connectors," you say. Maybe. But even simple interactions, such as message sending or procedure calls, become complex in their own right in most distributed settings. Moreover, why should one think that the particular choice of simple connector for one system will be the same for another? In one case, a simple connector might be a message send; in another, a procedure call; in another, an event announcement. Because no universal "simplest" connector exists, we always need to be clear about exactly what we are modeling when we describe an interaction.
Choosing Connector Abstractions
Choosing how to document connector abstractions is often one of the most difficult jobs of producing effective architectural documentation using the C&C viewtype. It may be difficult to map from the C&C views to more implementation-oriented views if the view documents only early, coarse-grained decisions and includes abstract connector types. The view becomes cluttered if it documents connectors and components that are logically part of the connector mechanism. Deciding on connector abstractions is a matter of taste and is influenced by the needs of the architecture stakeholders and architectural analysis. More than one view may be necessary. In today's systems, documentation tends to err on the side of being too implementation oriented.
To illustrate alternative choices in abstraction and representation with respect to connectors, consider the two forms of a simple publish-subscribe system shown in Figure 3.3. The first version shows five components communicating through an event bus, which describes an interaction that ensures that each published event is delivered to all subscribers of that event. The second version shows the same five components communicating with the assistance of a centralized dispatcher component responsible for distributing events via procedure calls to the other components.
Figure 3.3. Two potential versions of a publish-subscribe system. In Version 1, all communication takes place over an event bus; in Version 2, communication occurs with the assistance of a dispatcher component.
How do they compare? Version 1 clearly indicates the n-ary nature of the interaction: events announced by one component may be delivered to multiple subscribers. The connector encapsulates the underlying mechanism. This abstraction allows alternative implementations, such as a centralized dispatcher, multiple dispatchers, or even point-to-point distribution of events between components, and it simplifies the diagram. For some purposes, on the other hand, it may be important to show the underlying infrastructure. For example, to calculate event throughput, it might be necessary to reason about the properties of a centralized dispatch mechanism that carries out the communication. Version 2 provides these details and could be an implementation refinement of Version 1. Which mechanism you choose depends on the context.
Because picking connector abstractions is a difficult job, it is worth listing some guidelines to keep in mind.