If your experience with model-driven software development stops at class diagrams and generated skeletons, skepticism is understandable. Generating declarations and empty operation bodies can save some typing, but someone still has to implement the behavior. Maintaining the diagrams afterward can become another task competing for time. And in practice, when code is edited outside the model, the two do get out of sync, which tends to make people question the value of the model.
A more complete model-driven approach changes that arrangement. The model contains the implementation behavior, generation produces the source, and maintained changes go into the model. Design documentation comes from the same model.
I prefer working this way. Getting comfortable with it takes an investment, and the tools matter, but the approach deserves a closer look.
Modeling, In My Experience
My introduction to the Shlaer-Mellor model-driven approach came in 1998, during a presentation to a workplace technology special interest group. The presenter was modest about his results, but the productivity figures caught my attention. I continued learning about the techniques and later led a project developing a point-of-sale loyalty-rewards terminal using UML in Rational Rose with a model-driven software development add-in.
I subsequently moved to Sparx Enterprise Architect (EA), which gave me substantially more powerful transformation capabilities. I still do plenty of direct hand coding, but when the opportunity is there, I typically model it.
My preference comes from the complete workflow: analysis, architecture, detailed design, implementation, and documentation. Modeling is part of developing the implementation, and the same model produces both the source and the design documentation. When I make a change, I update the model and regenerate the outputs. Keeping them synchronized is built into the way I work.
Model-driven development inherited some legitimate skepticism. Earlier computer-aided software engineering (CASE) tools encountered difficulties matching their abstractions to implementation platforms and accommodating production constraints. Some generators were effectively vendor-controlled black boxes, limiting what engineers could change when the output did not meet their needs.
Round-trip engineering introduced another difficult problem: allowing changes in both models and source code, then keeping the two representations consistent. Differences in abstraction make that synchronization harder than simply copying a changed declaration from one place to another.
I have experimented with EA’s round-trip engineering support, although I have never used it to develop a project. In those experiments, EA generated skeletal code with marked regions for handwritten implementation. Synchronization brought that implementation back into the model’s operation behavior fields. The difficulty was that the source language could express constructs the parser did not accommodate, leaving portions that would not synchronize correctly. Newer language features and code written outside the patterns the parser expected made that limitation more apparent.
My development workflow does not depend on parsing edited source back into the model. I maintain the implementation in the model and generate the source from it. The generated source is available for inspection, analysis, compilation, testing, and debugging, but it is not an independently edited version of the software. The model is the single source of truth.
A model-authoritative workflow has its own obligations. The model and generation templates must contain everything needed to reproduce the generated implementation. A correction that survives only in an edited source file is unfinished work.
Three Levels of the Model
I organize the work into three levels: the computation-independent model (CIM), the platform-independent model (PIM), and the platform-specific model (PSM). Each lets me work with a different level of detail.
In the CIM, I focus on the problem space. Use cases and requirements establish what the system must accomplish, without deciding how the software will implement it.
In my workflow, structural analysis and design belong in the PIM. Here I establish the software’s organization, responsibilities, and relationships while leaving many implementation details unresolved. Constructors, property implementations, and language-specific data types generally do not need to be decided individually at this level.
The PSM is where I develop the detailed behavior in the context of the target platform. “Platform” here refers broadly to the target hardware, its peripherals, the operating system, APIs and libraries, and, of course, the programming language. Transformation templates generate language-specific structure from the PIM. In the PSM, I complete the platform-specific structure, add references to external frameworks and libraries, and develop the detailed behavior before generating the source.
I use the model terminology of the Object Management Group’s (OMG) Model-Driven Architecture (MDA). The underlying separation of application analysis from implementation mechanisms was already part of Shlaer-Mellor. Mellor subsequently helped carry executable modeling into the UML and OMG communities. With Marc Balcer, he published Executable UML: A Foundation for Model-Driven Architecture in 2002, adapting that lineage to UML notation with the execution semantics needed to make the models precise enough to run and translate.
I begin with use cases and develop requirements from them. This happens in the CIM. Those activities deserve their own discussion; for this article, they establish what the software must accomplish before I decide how to organize it. Even when requirements already exist, if traceability is a factor, I import them into the tool. This really simplifies documenting the requirements-to-design-to-code relationships.
I then establish domains, subsystems where needed, and their dependencies, following a Shlaer-Mellor-inspired approach. Domains separate subject matters; subsystems (or subdomains) provide a way to partition a domain into manageable pieces.
The early work is analysis: identifying concepts, responsibilities, relationships, and required behavior. Architectural design develops from that understanding as I establish the boundaries and interfaces through which the parts cooperate.
Dependencies deserve particular attention. A collection of named subsystems tells me relatively little until I understand what each owns, what services it requires, and what assumptions cross its boundaries.
With that context established, I work into the detail one modeled domain at a time. The architecture provides the surrounding structure without requiring every domain to be developed to the same depth simultaneously.
Separating the model into levels does not mean completing the entire design before implementing any of it. I complete an iteration at the current level before drilling down into the next level of detail. Moving back up a level, iterating there, and then drilling down again is a common part of the process. Working implementations also provide feedback for further iterations as the design develops.
Agile modeling principles fit that approach: model with a purpose, develop detail when it is needed, and check the result against working software. In my workflow, the detailed model remains the maintained implementation rather than becoming a sketch that is set aside when coding begins. I talk more about this in Adding Value with Agile Models: Applying Shlaer-Mellor Discipline to Agile Modeling.
Some developers prefer to skip the separate PIM and work directly in a single structural model that includes the implementation details. I did that when I was starting out. The tools I was using didn’t support model-to-model transformations, and coming from the Shlaer-Mellor school, I didn’t yet understand the concept. As I studied MDA, I soon learned the advantages of the PIM.
A separate PIM lets me focus on the structural design while the transformation templates handle recurring language-specific details when generating the PSM. EA includes transformation templates that are adequate for getting started. Over the years, I have refined them to handle particular needs and automate more of the implementation decisions I would otherwise make individually.
Learning the approach does not require making all those refinements at the outset.
The distinction between transformation and code generation is important. In EA, “transformation” refers to creating a model in a new form from an existing one. Here, the transformation translates the platform-independent structure into language-specific forms, with some expansion within individual classes. Depending on the templates, it can map data types, add constructors, specialize classes through stereotypes, and turn modeled properties into appropriate language constructs. My own templates have evolved over years, and I continue to refine and enhance them.
A PIM is not limited to a single transformation. The same model can produce an implementation PSM for C++, another for C#, and unit-test PSMs specific to each language. These transformations can be applied to a full model, or selected packages and elements.
Handling the Exceptions
Defaults cover the typical case, but a useful transformation also needs a way to handle exceptions. I can customize the PSM directly, although some modifications may be overwritten when design changes require another PIM-to-PSM transformation. The transformation preserves some kinds of PSM modifications, but its ability to do so has limits. Alternatively, I can provide information in the PIM that tells the transformation how to handle a particular element.
Tagged values provide one way to carry those instructions. A tagged value is a named piece of information attached to a model element, which a transformation template can make use of.
For example, I have needed a component implemented in both C++ and C#, with particular data types used in each language. I modified the transformation templates to look for tagged values named cppType and csType on attributes and parameters. When the appropriate tag was present, the transformation used its value instead of the default generated type.
The structural design could therefore remain shared while the exceptions were specified where needed. I did not have to abandon the default transformation rules or maintain two complete structural designs merely because some types required special treatment.
Where the Implementation Goes
At the implementation level, I use a platform-specific model, or PSM. The model incorporates the target language and platform decisions needed to produce the implementation.
For behavior modeling, I favor activity diagrams associated with the PSM’s class operations. For simple sequential behavior, the diagrams resemble familiar flowcharts. Within the action nodes, I typically write one or two lines of target-language code that implement the intended action. Where an activity diagram isn’t a good fit, there are alternatives, such as state diagrams.
Enterprise Architect’s support for this kind of behavioral generation is longstanding. Embedded Systems Development using SysML, published in 2010, describes generation from activity diagrams, including conditionals, loops, calls, and atomic actions containing implementation-specific statements.
I still select data types, define interfaces, handle errors, and work out the behavior. The model provides the maintained representation of those decisions.
When the goal is to produce code that is 100% model-generated, completing the PSM involves more than defining operation behavior. I often need supplemental classes, representations of external library interfaces, and dependencies connecting my classes to those external elements. For example, I might partially model the classes or interfaces (not the behavior) of portions of the C++ standard library, or if I’m dealing with C# on Mono, the .NET library. Linking to these external classes as dependencies is trivial, and modeling those dependencies allows the generator to produce the appropriate includes or other language-appropriate references.
Code generation then produces source from the completed platform-specific model. EA provides templates for generating both structure and behavior in languages including C++, C#, and Java. You can also create new template sets to support additional languages or specific dialects, although I would recommend starting with a supported language while learning the workflow.
A separate PIM is not required to generate a complete implementation from a model. I initially worked without one, although my current workflow uses a separate PIM and model-to-model transformations to automate recurring implementation details.
The Output Should Meet Your Standards
While the provided model transformation templates are adequate for starting out, I found the source-code generation templates to be a bit rougher around the edges. Their default output can be poorly styled and sparsely commented. I have customized those templates to produce code in my normal style, nearly identical to what I would write manually. The result is practically indistinguishable from code written carefully by hand.
Enterprise Architect’s template framework controls the translation from model elements to source, including behavioral code generation. Refining the templates takes effort, but the resulting decisions apply each time the code is generated. If the output needs different formatting or a different comment structure, I change the template. Manually cleaning up generated files would create recurring work and undermine regeneration.
Generated code should be something you are prepared to review and maintain through the model and templates that produce it. I consider it likely that a later owner of the code will not use a model-based approach. The source should stand on its own; there is no reason its model-generated origin needs to be apparent.
I also generate Doxygen comments from the model. Given the detailed design documentation available from the same model, Doxygen may seem redundant. It provides the engineering team with a familiar way to navigate the implementation, including engineers who do not use the modeling environment. Once the template additions are in place, maintaining the Doxygen comments requires no separate effort; they are regenerated along with the source.
For an initial evaluation, inspect the generated source critically. Check whether the behavior is correct, whether the implementation meets your constraints, and whether the templates give you sufficient control over the output. A successful generation command is only the beginning of that assessment.
Make a Change, Then Regenerate
Regeneration is the more revealing part of the experiment.
Once an operation behaves as expected, change its behavior in the model and generate it again. Check the source and documentation. You should be able to carry the change through without repairing the generated files by hand.
Debugging follows the same principle. I may investigate a failure in the source and experiment with a correction there. Before the correction becomes maintained software, I put it into the model and regenerate. The regenerated result is what I verify.
For generated code, permanent manual modifications are verboten. What gets committed as code is what the generation process produces.
Maintaining that rule keeps the implementation reproducible from the model. Allowing an occasional convenient patch would leave part of the implementation outside the representation that is supposed to generate it.
Why It Can Feel Slower
For an engineer accustomed to working directly in source code, modeling can seem slow at first. Learning the notation, navigating the tool, and deciding how to represent behavior all demand attention. Familiar editor operations are being replaced by unfamiliar modeling operations.
My experience is that, when starting out, the full analysis-to-code cycle can take about as long as the coding work you are accustomed to. There is still a benefit, since documentation rides along from the same model, without a separate effort to reconstruct the design afterward.
Generating documentation still requires me to put adequate descriptions into the model. The saving comes from maintaining that information alongside the design and implementation, then producing the outputs together.
With practice, modeling patterns become ingrained. Less effort goes into working out how to express each part, and more attention stays on the software itself. In my experience, productivity then increases by multiples. I don’t promise a particular multiplier for another engineer or project. My point is that the pace of the first modeling session is a poor measure of the productivity the approach can eventually provide.
A fair comparison also includes the work completed. Comparing model development and generated documentation against coding alone leaves part of the result out of the accounting.
Getting Started Takes an Investment
For an individual engineer, the investment includes both learning and tools.
For my workflow, I use Sparx Enterprise Architect. Unified Edition is the minimum needed for the behavioral generation discussed here; lower editions provide other code-engineering capabilities. Sparx Systems currently lists a standard Unified perpetual license at US $535, although readers should check the current price and edition features before purchasing.
BridgePoint provides another route into executable, translatable modeling. BridgePoint, available through xtUML.org, is a free, open-source environment for modeling, executing, and translating xtUML models. It carries forward the Shlaer-Mellor lineage discussed earlier and includes an editor, model verifier, and model compilers. It is much closer to the original Shlaer-Mellor paradigm, and while it is UML, the structure of the models feels a bit foreign to me. On the other hand, this may be the most mature tool suite in existence for generating code from UML models.
The investment extends beyond learning the tool and tailoring its templates. Watching a model generate working source can look almost like magic, particularly if you have never modeled software yourself. The visible steps can suggest that a model can be assembled quickly and everything else will follow.
Modeling still requires a software development mindset. It isn’t as easy as many expect. A model is much larger than its diagrams. The diagrams show selected views; relationships connect the underlying elements, and those elements carry meanings and properties that may not be visible on the page. Drawing the boxes and lines is only part of constructing the model.
Learning to work this way therefore involves learning how to think about models, as well as how to operate the tool. You need to understand what belongs at each level of abstraction, how the elements relate, and what information the transformations require. My experience with productivity gains comes from developing that fluency, not simply from becoming faster at drawing diagrams.
Sharing a model with other engineers introduces repository and deployment decisions. My EA Deployment Playbook addresses those choices for individuals and small teams. It covers the working environment rather than teaching UML or behavioral code generation.
Give it a Fair Trial
For someone investigating the approach, a bounded component is a more manageable starting point than an entire application. Choosing behavior you already understand lets you concentrate on learning how to express and generate it. Include enough operational behavior to exercise the generation capabilities; generating an empty class proves very little about whether the workflow will suit your work.
Establish the component’s requirements and boundaries, model its structure and operational behavior, and generate both source and design documentation. Then make a correction through the model and regenerate.
Include the documentation when assessing the effort. Also distinguish the time spent learning the environment from the work you would repeat once familiar with it.
Model-Driven Discipline
Keeping the outputs synchronized does not establish that the software is correct. Requirements review, design review, code analysis, and testing remain necessary. Tool limitations and unsuitable output are valid reasons to question whether a particular environment meets your needs.
The model is the single source of truth. A correction made there becomes part of the maintained design and carries through to the generated source and documentation. Engineering discipline is always demanding, but modeling need not make it harder. Maintaining those decisions together saves me from carrying the same correction into separately maintained representations of the software.