3.5. Designing a variable architecture

A Product Line architecture will only rarely result directly from the structure of the problem space model. The solution space which can be implemented should support the variability of the problem space, but there won't necessarily be a 1:1 correspondence of the feature models with the architecture. The mapping of variabilities can take place in various ways.

In the example Product Line we will use a simple object-oriented design concept implemented in C++ . A majority of the variability is then resolved at compile-time or link-time; runtime variability is only used if it is absolutely necessary. Such solutions are frequently used in practice, particularly in embedded systems.

The choice of which tools to use for automating the configuration and / or production of a variant plays a substantial role in the design and implementation of the solution space. The range of variability, the complexity of relations between problem space features and solution constituents, the number and frequency of variant production, the size and experience of the development team and many further factors play a role. In simple cases the variant can be produced by hand, but quickly automation in the various forms like small configuration scripts, model transformers, code generators or variant management systems such as pure::variants will speed production.

For modelling and mapping of the solution space variability pure::variants and its integrated model transformation in most case is an ideal. This uses a Family Model to model the solution space, to associate solution space elements with problem space features, and to support the automatic selection of solution space elements when constructing a product variant.

Family models have a hierarchical structure, consisting of logical items of the solution architecture, e.g. components, classes and objects. These logical items can be augmented with information about "real" solution elements such as source code files, in order to enable automatic production of a solution from a valid feature model configuration (more on this later). For each family model element a rule is created to link it to the solution space. For example, the Languages implementation component is only included if the Languages feature has been selected from the problem space. To achieve this, a Languages rule is attached to the "Languages" component . Any item below “Languages” in the Family model can only be included in the solution if the corresponding Languages feature is selected.

A pure::variants screen shot showing part of the solution space is shown in Figure 3.5, “pure::variants screen shot - solution space fragment shown at right” .

Figure 3.5. pure::variants screen shot - solution space fragment shown at right

pure::variants screen shot - solution space fragment shown at right


In our example, an architectural variation point arises, among other possibilities, in the area of data output. Each output format can be implemented with an object of a format-specific output class. Thus in the case of English output, an object of type EnglishOutput is instantiated, and with German output, an GermanOutput object. There would also be the possibility here of instantiating an appropriate object at runtime using a Strategy pattern. However, since the feature model designates only the use of alternative output formats, the variability can be resolved at compile-time and a suitable object can be instantiated using code generation for example.

In our example solution space a lookup in a text database is used to support multiple natural languages. The choice of which database to use is made at compile-time depending on the desired language. No difference in solution architectures can be detected between two variants that differ only in the target language. Here the variation point is embedded in the data level of the implementation. In many cases managing variable solutions only at the architectural level is insufficient. As has already been mentioned above, we must also support variation points at the implementation level, i.e. in our case at the C++ source code level. This is necessary to support automated product derivation. The constituents of a solution on the implementation level, like source code files or configuration files which can be generated, can also be entered in the family model and associated with selection rules.

So the existence of the Languages component in a product variant is denoted using a #define preprocessor directive in a configuration Header file. In addition, an appropriate abstract variation point variable "Languages" must first be created of the type ps:variable in the family model. The value of this variable is determined by a Value attribute. In our case this value is always 1 if the variable is contained in the product variant. An item of type ps:flagfile can now be assigned to this abstract variable. This item also possesses attributes (file, flag), which are used during the transformation of the model into "real" code. The meaning of the attributes is determined by the transformation selected in the generation step . Here we use the standard pure::variants transformation for C / C++ programs, which produces a C-preprocessor #define- Flags in the file defined by file from these specifications.

Separating the logical variation point from the solution makes it very simple to manage changes to the solution space. For example, if the same variation point requires an entry in a Makefile, this could be achieved with the definition of a further source element, of the type ps:makefile, below the variation point "Languages".