Model-Driven Architecture using JET2 in Rational Software Architect or Rational Software Modeler
Автор: Dev Bhattacharyya, Senior IT Architect, IBM[ Электронный источник: читать ]
But let's talk about it. Wouldn't it be nice…?
—The Beach Boys
Wouldn't it be nice if you were to take the initial requirements document for a project and be able to accurately estimate the design and development effort? This article tells you how to transform a Unified Modeling Language (UML) model created in IBM® Rational® Software Architect into a dynamic estimation and feedback engine. Thanks to the tremendous growth in Model-Driven Architecture (MDA) and design, static nondescript images, models, and diagrams are now close to the point they can be executed. Rational Software Architect and IBM® Rational® Software Modeler are key proponents of Java Emitter Templates (JET) and UML modeling. This article attempts to go beyond the traditional code generation to more useful systems, such as estimation.
The JET projects in this article describe these tasks:
- Estimating at the requirements and analysis stage
- Comparing estimations as the model matures, at the design stage
- Generating feedback to the model
Models provide vivid insight into how business problems can be expressed through notational UML. A typical requirements gathering includes these components:
- Requirements and business behaviors expressed through use cases and interactions
- Processes and goal modeling illustrated by activity diagrams
- Messaging expressed through structural models, such as Class and Component models
Models evolve and mature over time. A typical model that started as just requirements may also include structural classifier models that express interactions. A use case model is a perfect place to start the estimation process.
The Eclipse Modeling Framework (EMF), which is an integral part of Rational Software Architect (and Rational Software Modeler, contains powerful tools for generating source code and documents. One of them is JET (Java Emitter Templates). With JET, you can use a syntax that is somewhat like JavaServer™ Pages (JSP) to write templates and run them against models.
The article is intended for readers who have a reasonable working knowledge of Rational Software Architect. It is also based on the assumption that the reader understands XML, Java™ technology, and XPATH scripting syntax and semantics.
First transformation: project estimation from use cases
A portion of this article is based on the findings that Capers Jones describes in Chapter 7 of his book, Estimating Software Costs: Bringing Realism to Estimating, Second Edition (see Resources). See Chapter 7, "Manual Estimating Methods Derived from Agile Projects and New Environments." He states that a use case represents an average of roughly 35 function points, although it can vary between 15 and 75 function points. A typical application of 1500 function points (roughly 75,000 Java statements), would require about 42 use case diagrams. A single use case roughly defines the usage pattern of about 1785 Java statements.
Use case estimation was baselined in 1998-2001. Much of thought leadership came from Schneider and Winters in1998 and Alistair Cockburn in 2001 (see Resources). This article assumes that a project team that wants to estimate with use case points writes their use cases at goal levels. Each use case therefore has a goal. The goal of a user goal-level use case is analogous to a unit of business value.
Use case points are an accumulation of total numbers of actors, relationships, and other aspects of use case methodology that indicate a value for the project being estimated, thus forming the basis for the JET2 estimation engine. After the size in use case points is determined, effort and schedule algorithms can then be applied.
Complexity of actors and use cases
Each use case represents an average of 35 function points. Figure 1 shows a simple project as outlined and created in Rational Software Architect.
Figure 1. Use case example
You can use Table 1 to determine unadjusted weighted values of use cases and actors.
Table 1. Computing unadjusted use case points (actors)
Actor type | Description | Weighting factor | Number of actors | Total |
---|---|---|---|---|
Simple | External system with well-defined API | 1 | WF * Number | |
Average | External system using a protocol-based interface: HTTP or TCP/IP or database | 2 | WF * Number | |
Complex | Human | 3 | WF * Number | |
Unadjusted actor weight total | Total |
Table 2. Computing unadjusted use case points (use cases)
Use case type | Description | Weighting factor | Number of use cases | Total |
---|---|---|---|---|
Simple | 1-3 transactions | 5 | WF * Number | |
Average | 4-7 transactions | 10 | WF * Number | |
Complex | More than 7 transactions | 15 | WF * Number | |
Unadjusted use case weight total | Total |
To analyze a use case by using this table, the actors and use cases are stereotyped with keys: simple, average, or complex.
A separate JET project must be created to run against the model JET transformation. Start from the New Project wizard:
- From main menu, click File > New > Project.
- In the JET Transformations wizard, select JET Transformation Project.
- Enter a project name. For this example, use
Estimation
. - Click Finish.
The JET project created has a template folder that contains two files:
-
main.jet
-
dump.jet
Because the project will be addressing different types of estimations, create two separate JET templates: one for use case point analysis and another for class point analysis.
The counters for the different types of actors and use cases are enclosed in the Java scriplet section: <% %>
The code scans the model for use cases and actors. When it encounters them in the model, it checks for the stereotype key and increments the respective counters accordingly, as code Listing 1 illustrates.
Listing 1. Use case point analysis
<%-- // Initialize Variables. --%> <% int countUCComplex = 0; int countUCAverage = 0; int countUCSimple = 0; int countActorComplex = 0; int countActorAverage = 0; int countActorSimple = 0; %> <%-- // Get the name of the model --%> <c:setVariable var="model" select="/Model" /> Project Estimates for Model: <c:get select="$model/@name" /> <%-- // iterate through all packages --%> <c:iterate select="//Package" var="package" > <c:if test = "//Package[self::Package]" > Packages: <c:get select="$package/@name" /> <c:setVariable select="$package" var="source"/> <%-- // iterate through all use cases in the package --%> <c:iterate select="$package/packagedElement[self::UseCase]" var="useCase" > Use Case: <c:get select = "$useCase/@name" /> <%-- get the stereotypes --%> <c:iterate select="$useCase/eAnnotations" var="stype" > <c:iterate select="$stype/details" var="details" > <c:setVariable select="$details/@key" var="key"/> Type: <c:get select = "$key" /> <c:choose> <c:when test = "$key = 'complex'"> <% ++countUCComplex; %> </c:when> <c:when test = "$key = 'average'"> <% ++countUCAverage; %> </c:when> <c:otherwise> <% ++countUCSimple; %> </c:otherwise> </c:choose> </c:iterate> </c:iterate> </c:iterate> <%-- // Iterate through all use case actors in the package --%> <c:iterate select="$package/packagedElement[self::Actor]" var="actor" > Actor: <c:get select = "$actor/@name" /> <%-- get the stereotypes --%> <c:iterate select="$actor/eAnnotations" var="stype" > <c:iterate select="$stype/details" var="details" > <c:setVariable select="$details/@key" var="key"/> Type: <c:get select = "$key" /> <c:choose> <c:when test = "$key = 'complex'"> <% ++countActorComplex; %> </c:when> <c:when test = "$key = 'average'"> <% ++countActorAverage; %> </c:when> <c:otherwise> <% ++countActorSimple; %> </c:otherwise> </c:choose> </c:iterate> </c:iterate> </c:iterate> </c:if> </c:iterate> **Totals** Simple Use Cases: <%= countUCSimple %>, UC Weight: <%= countUCSimple*5 %> Average Use Cases: <%= countUCAverage %>, UC Weight: <%= countUCAverage*10 %> Complex Use Cases: <%= countUCComplex %>, UC Weight: <%= countUCComplex*15 %> Simple Actors: <%= countActorSimple %>, Actor Weight: <%= countActorSimple*1 %> Average Actors: <%= countActorAverage %>, Actor Weight: <%= countActorAverage*2 %> Complex Actors: <%= countActorComplex %>, Actor Weight: <%= countActorComplex*3 %> |
Computing total unadjusted points and person hours
The total unadjusted points are the sum of all individual totals:
Unadjusted use case points (UUCP) = Unadjusted actor weight (UAW) + Unadjusted use case weight (UUCW), thus:
UUCP = UAW + UUCW
To calculate the person hours, you must compute the technology complexity and the environment, as Listing 2 shows.
Listing 2. Computing unadjusted use case points
<% int totalWeight = countUCSimple*5 + countUCAverage*10 + countUCComplex*15 + countActorSimple*1 + countActorAverage*2 + countActorComplex*3; %> Unadjusted Use Case Points = <%= totalWeight %> |
Assuming a Technical Complexity Factor of 1.075, an Experience Factor of 0.545, and Experience / Stability Index of 7, the unadjusted person hours can be computed as Listing 3 shows.
Listing 3. Unadjusted person hours
<% double personHours = (20 * totalWeight) * 1.075 * 0.545; %> Unadjusted Person Hours = <%= personHours %> |
You can allocate the Person Hours to the different IBM® Rational Unified Process® (RUP®) phases, as the example in Listing 4 shows.
Listing 4. Allocation to different phases
Project Life Cycle Inception (5%) = <%= personHours*0.05 %> Hours Elaboration (20%) = <%= personHours*0.2 %> Hours Construction (65%) = <%= personHours*0.65 %> Hours Transition (10%) = <%= personHours*0.1 %> Hours |
Extended transformation: class point analysis
The class point approach provides system-level estimation. The basic ideas of the Class Point method were first proposed by Costagliola, Ferrucci, Tortora, and G. Vitiello (see Resources). The aim of the proposed approach is to provide a method that we can use to refine estimates throughout the development and to exploit new information as it becomes available.
Because function point counters frequently have to deal with specifications that contain use cases, some preliminary data is also available from them.
Identification and classification of user classes
During the first step of the class point counting, the design specifications are analyzed in order to identify and classify the classes.
External Methods tell you the size of the interface of a class. It is determined by the number of locally defined public methods. Services Requested provide a measure of the interconnection of different system components. It is again applicable to a single class and is determined by the number of different services requested from other classes.
Computing total unadjusted class points
The Class model and the Interaction diagram trace back to the use case described here previously, as Figures 2 and 3 illustrate.
Figure 2. Class model
Figure 3. Interaction diagram
Table 3. Complexity and weighting for external methods and services requested
Weighting | Complexity |
---|---|
Up to 8 External Methods and Single Service Request | Simple |
More than 8 on a Single Service Request | Average |
Up to 4 External Methods on less than 4 Service Requests | Simple |
Between 5 and 8 External Methods on less than 4 Service Requests | Average |
More than 8 External Methods on less than 4 Service Requests | High |
Up to 4 External Methods for 4 or more Service Requests | Average |
All others | High |
The next steps involve computing from the different models described above - Use Case, Class model and Interaction diagram showing the flow of messages. Listing 5 is the JET template that does the work. To compute the External Methods and Service Requests, one will have to iterate multiple times. A Java vector has been instantiated that does the carry-over of necessary data. A couple of interesting ways to share data between the template and Java code have also been described. As Listing 5 illustrates, the code traverses first through the Class model and then through the Interaction model, computing the external methods and service requests as it progresses.
Listing 5. Class point analysis
<%@jet imports="java.util.*" %> <% class Additions { public String className; public int nem; public int nsr; public String lifeLine; } java.util.Vector modelClass = new java.util.Vector(); java.util.Vector l = new java.util.Vector(); %> <c:setVariable select="/Model" var="modelName" /> Model Name: <c:get select="$modelName/@name" /> <c:iterate select="//Package" var="package"> Package Name: <c:get select="$package/@name" /> <%-- Iteration through all classes --%> <c:iterate select="$package/packagedElement[self::Class]" var="class" > Class: <c:get select="$class/@name" /> <c:setVariable var="clName" select="$class/@name" /> <% int nems = 0; %> <c:iterate select="$class/ownedOperation" var="oper" > Operation: <c:get select="$oper/@name" /> Visibility: <c:get select = "$oper/@visibility" /> <c:if test="$oper/@visibility='public'" > <% ++nems; %> </c:if> </c:iterate> <c:if test="$class/ownedOperation"> <% Additions a = new Additions(); a.className = org.eclipse.jet.xpath.XPathUtil.xpathString (context.getVariable("clName")); a.nem = nems; modelClass.addElement(a); %> </c:if> </c:iterate> <%-- Iteration through all Interactions --%> <c:iterate select="$package/packagedElement[self::Collaboration]" var="collab" > Collaboration: <c:get select="$collab/@name" /> <c:iterate select="$collab/ownedBehavior[self::Interaction]" var="interact" > Interaction: <c:get select="$interact/@name" /> <c:iterate select="$interact/lifeline" var="lline" > <c:setVariable var="llName" select="$lline/@name" /> Lifeline: <c:get select="$lline/@name" /> Represents: <c:get select="$lline/represents/@name" />, <c:get select="$lline/represents/type/@name" /> <c:setVariable var="repClass" select="$lline/represents/type/@name" /> <% boolean notFound = true; for (int i = 0; i < modelClass.size(); i++) { Additions a = (Additions) modelClass.elementAt(i); String repClassName = org.eclipse.jet.xpath.XPathUtil.xpathString (context.getVariable("repClass")); if (repClassName.equals(a.className)) { a.lifeLine = org.eclipse.jet.xpath.XPathUtil.xpathString (context.getVariable("llName")); modelClass.setElementAt(a, i); notFound = false; } } if (notFound) { Additions a = new Additions(); a.className = org.eclipse.jet.xpath.XPathUtil.xpathString (context.getVariable("repClass")); a.lifeLine = org.eclipse.jet.xpath.XPathUtil.xpathString (context.getVariable("llName")); modelClass.addElement(a); } %> </c:iterate> <c:iterate select="$interact/fragment" var="frag" > Fragment (covered): <c:get select="$frag/covered/@name" /> <c:setVariable var="llname" select="$frag/covered/@name" /> <c:if test="$frag/message"> Message: <c:get select="$frag/message/@name" /> Type: <c:get select="$frag/message/@messageSort" /> <c:if test="$frag/message/@messageSort='synchCall'"> <% String lifeLine = null; for (int i = 0; i < modelClass.size(); i++) { Additions a = (Additions) modelClass.elementAt(i); lifeLine = org.eclipse.jet.xpath.XPathUtil.xpathString (context.getVariable("llname")); if (lifeLine.equals(a.lifeLine)) { ++a.nsr; modelClass.setElementAt(a, i); } } %> </c:if> </c:if> <c:if test="$frag/start"> Start: <c:get select="$frag/start/message/@name" /> </c:if> <c:if test="$frag/finish"> Finish: <c:get select="$frag/finish/message/@name" /> </c:if> </c:iterate> <c:iterate select="$interact/message" var="msg" > Message: <c:get select="$msg/@name" /> Receive Event: <c:get select="$msg/receiveEvent/message/@name" /> Send Event: <c:get select="$msg/sendEvent/message/@name" /> </c:iterate> </c:iterate> </c:iterate> </c:iterate> Totals: <% int countSimple = 0; int countAverage = 0; int countComplex = 0; for (java.util.Enumeration e = modelClass.elements(); e.hasMoreElements();) { Additions a = (Additions) e.nextElement(); %> Class: <%= a.className %>, NEMS <%= a.nem %>, Lifeline <%= a.lifeLine %>, NSR <%= a.nsr %> <% if ((a.nem <= 8) && (a.nsr <= 1)) ++countSimple; else if ((a.nem <= 4) && (a.nsr >= 2) && (a.nsr <= 3)) ++countSimple; else if ((a.nem <= 4) && (a.nsr > 3)) ++countAverage; else if ((a.nem >= 5) && (a.nem <= 8) && (a.nsr >= 2) && (a.nsr <= 3)) ++countAverage; else if ((a.nem > 8) && (a.nsr >= 2) && (a.nsr <= 3)) ++countAverage; else ++countComplex; } %> Counts: Simple: <%= countSimple %> Average: <%= countAverage %> Complex: <%= countComplex %> <% int tucp = (countSimple * 5) + (countAverage * 8) + (countComplex * 13); %> TUCP (Total Unadjusted Class Points) = <%= tucp %> </c:log> |
After you know the total unadjusted points, it is easy to add the other factors to arrive at the unadjusted person hours, just as you did for the use case point analysis.
This article walked you through most of the key areas of a UML model and how you can extract, transform, and create artifacts that add to intelligent reporting. You can do similar extractions by using BIRT (Business Intelligence and Reporting).