Domain-Specific Modeling

Dr. Dobb's Journal August 2002

Improving productivity and time to market

By Risto Pohjonen and Steven Kelly

Risto is a developer and Steven chief technical officer at MetaCase Consulting in Finland. They can be contacted at risto.pohjonen@metacase.com and steven.kelly@metacase.com, respectively.

Traditionally, software development has been a series of mappings from the domain idea, to design models, and on to source code. These mappings tend to be slow and lead to errors and duplication of effort in problem solving, design, and coding. Domain-specific modeling (DSM) addresses these problems by removing the resource-intensive and error-prone mappings, aiming to solve the problem only once at the same level of abstraction with the domain itself.

While traditional modeling languages aim to be as generic as possible, a DSM language aims to be as specific as possible. This raises the level of abstraction of the models, reduces the amount of information that needs to be modeled, reduces mapping by moving the modeling language closer to the domain as perceived by designers, and improves the quality and scope of code generators. A domain is thus often as small as a given range of products of a given company. The more specific the domain can be, the higher the productivity benefits of applying DSM.

To illustrate the DSM approach, consider the watch example in Figure 1 (the complete example is available electronically; see "Resource Center," page 5, and http://www.metacase.com/). Assume in this example that you want to develop small applications such as stopwatches or alarms for digital wristwatches. By applying the DSM approach, you can start the development of a new application by focusing not on the implementation, but on the essence of your application — its desired behavior. The behavior is defined according to a domain-specific modeling language that directly contains such domain concepts as buttons, alarms, display icons, and time arithmetic operations. The modeling language can also leverage more traditional elements, such as the idea of states here for an embedded UI.

Figure 1: Example of domain-specific modeling

The model in Figure 1 represents the behavior of the time-setting feature with pure domain concepts: user actions triggered by pressing buttons, the display elements blinking while being edited, and the operations that increment and decrement the displayed time. As you can see, the implementation details (in this case Java) are hidden from developers. However, as the model captures all required static and dynamic aspects of the application, it is possible to generate 100 percent of the code for a fully functional watch application from the models.

Real-world experiences with DSM (at Nokia, Lucent, USAF, NASA, and so on) have shown major improvements in productivity, time-to-market responsiveness, and a significant reduction of the number of errors (see "A Software Engineering Experiment in Software Component Generation," by R. Kieburtz, et al., Proceedings of 18th International Conference on Software Engineering, IEEE Computer Society Press, March 1996; "Nokia Case Study, http://www.metacase.com/; Software Product-line Engineering, D. Weiss, et al., Addison-Wesley-Longman, 1999). Nokia, for instance, claims it now develops mobile phones up to 10 times faster using DSM than with traditional approaches.

Building a DSM Environment

As Figure 2 illustrates, three things are required for a complete DSM environment:

Figure 2: DSM environment.

For defining and implementing such an environment, you need an experienced developer who understands the domain and how to program for it. This expertise is made explicit and shared among the rest of the development team by the DSM environment.

The first requirement — a domain-specific modeling language and its tool support — can be met by adopting a customizable metaCASE environment like MetaEdit+ (from Metacase Consulting, where we work, http://www.metacase.com/) or Ptech modeling and code generation tools (from Ptech Inc., http://www.ptechinc.com/). Flexible tools such as these let you define and build your own DSM modeling languages, as well as providing you with CASE tool support. The more the metaCASE tool automates, the more you can concentrate on the modeling language rather than its implementation.

The modeling language definition is based on domain concepts identified during the domain analysis. For instance, examination of the example watch domain might reveal such concepts as display, button, icon, and time unit. The modeling language thus probably contains such concepts, along with ways to link them together. The goal here is to map the chosen concepts accurately to the domain semantics. As for the notational part of the language, you also have to define graphical representations for your concepts.

Concepts and symbols alone do not make a complete language definition. Usually, there are a number of domain rules to guide and enforce the development of the products within the domain. These rules typically constrain the use of the modeling language by defining the legal relationships between concepts and how certain concepts should be used. Once defined, the modeling language — enacted by the supporting tool — guarantees that all developers follow the same domain rules. This significantly reduces the possible design space — the kinds of applications that can be written with this language — and helps to ensure that designers only make appropriate applications. Should the range of applications need to be extended, the modeling language can, of course, be extended later.

The two remaining parts of the DSM environment — the code generator and framework code — must bridge the gap between the models on the higher level of abstraction and the lower level platform on which the code will run. The important question here is the division of labor between models, code generator, and framework code. In general, the models should be used only to capture the behavioral logic of the product while the framework should provide a well-defined set of services for the code generator to interface to. If these requirements are met, no additional intelligence is required during the code generation and the code generation can be kept reasonably straightforward.

Software Architecture for DSM

As you can see, the code generator relies heavily on the services and hooks provided by the software architecture embodied in the framework code. This emphasizes the role of the architecture as a key factor for a successful DSM environment. But what makes for a good architecture for DSM?

The main goal when building an architecture for DSM is to raise the level of abstraction by hiding the implementation complexity from developers. In DSM architectures, there are three basic levels on which this can be done, and generated code can use all three:

There are several examples of the lowest level strategy for raising the level of abstraction in the watch example. For example, the alarms in the watch example utilize a fairly complex thread-based Java implementation. To hide this complexity, we created a class called Alarm to provide a simple service interface for setting/ stopping alarms. All references from the code generator to alarms were defined using this interface. Similarly, a class called METime makes up for the shortcomings of the date and time implementation of the Java version used and provides the time unit operations needed for the watch applications. (This was for JDK 1.1.7. Although Java2's Timer and Calendar classes could now be used, having our own classes has insulated us from the differences between Java versions.) Figure 1 provides an example of how the services provided by the METime class are utilized. While setting the alarm or editing the displayed time, users typically roll certain time units upwards or downwards, as presented in the example. During code generation, when such an action is encountered, the code generator produces a simple dispatch method call to the service provided by the METime class.

An example of the second-level strategy is the overall structure of a watch application. The fundamental computational model behind a single watch application is a state machine. In the watch example, this state machine is implemented in two parts. First, there is a defined abstract superclass called AbstractWatchApplication that provides the general features and support functions for the state machine implementation. The second part of the state machine is automatically generated according to the respective watch application model. During the code generation, a new watch application class is subclassed from AbstractWatchApplication; for instance, Listing One is the generated code for the model in Figure 1. The subclass specifies the structure of this state machine (largely in data in the constructor) and the actions to be executed during the state transitions (behavioral code in the switch statement).

The highest level strategy — configuration — is used in the watch example in the "logical watch" concept. A logical watch defines a set of watch applications that belong to a certain watch model. For this purpose, the example watch architecture applies the same state machine approach as for the watch applications. However, the state machine on the model level defines only the applications and the transitions between them. When an application is activated, an application-level state machine is invoked and gains control. When the execution of the application-level state machine terminates, control is passed back to the higher level state machine. Two logical watches are at the top of Figure 3. There, it follows the transition to the next application.

Figure 3: Reuse on model component level. 

The Game of Reuse

Reuse is an important issue related to building an architecture for DSM. It is not only desirable to emphasize reuse in DSM but usually necessary, as you often want or need to reuse existing software artifacts for the DSM or its architecture. In addition to its high degree of plain code-level reuse, DSM also promotes the reuse of software designs and solutions. This lets DSM practitioners take full advantage of the "one source" principle: There is only one canonical source of information, the model, from which the code for various target platfo rms and documents is generated. Basically, there are three reuse mechanisms you can apply within the DSM environment:

Conclusion

Organizations wishing to adopt the DSM approach face two questions: Does their current software architecture fit this approach, and what kind of changes might be required? Fortunately, there is little magic in building an architecture for DSM. The true art of DSM — or any other — architecture is to emphasize good software design practices and domain knowledge. Thus, in most cases, any well-designed and well-built software architecture provides a suitable background for DSM. It is important to understand that DSM also benefits the architecture itself, as it ensures that products really are developed according to the architecture. DSM also provides a means for leveraging the experienced developer's knowledge about the architecture among the developer team. Both of these benefits directly contribute to the productivity and quality improvements.

DDJ

Table One: Productivity improvements of DSM modeling vs. manual coding.

Subjects implemented the same ‘stopwatch lap-time' feature by DSM with code generation, and again by directly writing Java code in the same DSM framework. Writing Java code without the DSM framework would have taken even longer. Try the task for yourself: download the evaluation version!

 

Coding

Modeling

Modeling:Coding productivity ratio

Time (s)

Features/hr

Time (s)

Features/hr

Senior developer

200

18.0

38

94.7

5.3 : 1

Junior developer

639

5.6

160

22.5

4.0 : 1

Total

 

23.6

 

117.2

5.0 : 1

Listing One

// All this code is generated directly from the model.
// Since no manual coding or editing is needed, it is
// not intended to be particularly human-readable

public class SimpleTime extends AbstractWatchApplication {

  // define unique numbers for each Action (a...) and DisplayFn (d...)
  static final int a22_1405	= +1; //+1+1
  static final int a22_2926	= +1+1; //+1
  static final int d22_977	= +1+1+1; //

  public SimpleTime(Master master) {
    super(master);

    // Transitions and their 
triggering buttons and actions
    // Arguments: From State, Button, Action, To State
    addTransition ("Start [Watch]", "", 0, "Show");
    addTransition ("Show", "Mode", 0, "EditHours");
    addTransition ("EditHours", "Set", a22_2926, "EditHours");
    addTransition ("EditHours", "Mode", 0, "EditMinutes");
    addTransition ("EditMinutes", "Set", a22_1405, "EditMinutes");
    addTransition ("EditMinutes", "Mode", 0, "Show");

    // What to display in each state
    // Arguments: State, blinking unit, central unit, DisplayFn
    addStateDisplay("Show", -1, METime.MINUTE, d22_977);
    addStateDisplay("EditHours", METime.HOUR_OF_DAY, METime.MINUTE, d22_977);
    addStateDisplay("EditMinutes", METime.MINUTE, METime.MINUTE, d22_977);
  };

  // Actions (return null) and DisplayFns (return time)
  public Object perform(int methodId)
  {
    switch (methodId) {
      case a22_2926:
        getclockOffset().roll(METime.HOUR_OF_DAY, true, displayTime());
        return null;
      case a22_1405:
        getclockOffset().roll(METime.MINUTE, true, displayTime());
        return null;
      case d22_977:
        return getclockTime();
    }
    return null;
  }
}

DDJ

Back to Article

CMP logo  

Copyright © 2004 CMP Media LLC, Dr. Dobb's Journal's Privacy Policy, Terms of Service,
Comments: webmaster@ddj.com
SDMG Websites: BYTE.com, C/C++ Users Journal, Dr. Dobb's Journal, MSDN Magazine, Sys Admin, SD Expo, SD Magazine, Unixreview, Windows Developer Network, New Architect