Disclaimer: This post reflects the point-at-time nature of the tools, languages and syntax used to model an example domain. The example works with Oslo SDK October 2008 CTP. By no means, I have attempted to author a complete domain in this post.
The objective of this post is to demonstrate an end-to-end scenario using various Microsoft Codename “Oslo” Tools. The domain being used in the example scenario is Use Case Specification. Use cases are one of the methods used in capturing functional requirements of a software system or its components. After we have modeled the use case domain, the end users will be able to use the following tools to work with this domain:
Custom Textual Language
We will use “M” Grammar to decode data from a custom “Use Case Textual DSL” that conforms to a pre-defined Use Case template. Let’s first start with a very simple custom format, which we can eventually evolve to be close to Alistair Cockburn’s Use Case Template for letting end-users create use case instances using this DSL. The end-user can use “Quadrant” Intellipad to write the use case instances
“Quadrant” Use Case Editor
“Quadrant” provides a mechanism for end-user customizable visualization of assets in the Oslo repository database, in which the UX can be driven by the models in the repository. Just like UML Use Case Diagrams present a graphical overview of the use cases, Quadrant can provide one or more data visualizations (visual notations) over the use case schema defined using M Language. The end user can use the out-of-box visualizations to edit either a single use case instance (using Property Sheet or Diagram Editor) or a collection of use case instances (using List or Table editor). But if the out-of-box visualizations don’t provide the best end-user experience, these visualizations can be customized by the domain author.
Note: The “Quadrant” bits in PDC VPC only support pre-built M Schemas, so unfortunately you will have to wait to try to use Quadrant to visually edit the Use Case model instances. Since most people, won’t be able to try this out themselves yet, I will show very basic visual editor customizations over the Use Case model schema.
If you are ready to get started, let’s put our System Integrator (SI) or Independent Service Vendor (ISV) developer hat on and create an end-to-end Oslo Use Case Domain for your end-users. Let me stress again, we are doing all this work to build a tangible ‘Use Case Editor Application’ for our end user. Let’s name the end user for Use Case domain Wendy. Wendy is a Systems Analyst in Company XYZ and would like to capture the system requirements and store them into a repository. Once we are done creating this domain, we can give “Quadrant” to her so she can begin to browse/create/edit/delete Use Cases using a mix of graphical and textual experiences.
Here are the steps we will do:
Step #
Category
Description
Tool / Language
1
Schematize
a
Create model schema for the Use Case domain
MSchema/Intellipad
b
Compile the model schema into an “M” Image
M.exe
c
Install Use Case “M” Image in “Oslo” Repository
MX.exe
2
Instance Creation (use M Instance)
Create sample model instance(s) for Use Case domain
MInstance/Intellipad
Compile the model instance into an MImage
Install Use Case data “M” Image in “Oslo” Repository
3
Instance Creation (use Textual DSL)
Determine the input format & create sample input
Create “M” grammar
MGrammar/Intellipad
Compile grammar language definition into an “M” Image
MG.exe
d
Generate “M” instance output for the sample input
MGX.exe
e
Repeat step 2b & 2c to load instances in “Oslo” Repository
M Tool Chain
4
Instance Creation (use “Quadrant” Visual Modeling Tool)
Extend “Quadrant” - Create viewer configuration instances
Quadrant
Use “Quadrant” to create more instances
Let’s first start with a very simple Use Case model, which we will evolve into more complete rich model.
Use “Oslo” Intellipad tool to create M Schema file. Save the file as SimpleUseCase.m.
module SimpleUseCaseSpecification
{
export UseCase, UseCases;
UseCases : UseCase*;
type UseCase
Id : Integer64 = AutoNumber();
Name : Text;
SuccessScenario : Text;
Goal : Text?;
PrimaryActor : Text?;
} where identity Id;
}
This model is very simple with one type (UseCase) and one extent (UseCases). It doesn’t use any complex nested types, either.
The T-SQL for above schema is as follows. In Intellipad, use M Mode -> Generic T-SQL Preview to view the SQL generated for an M Schema.
set xact_abort on;
go
begin transaction;
set ansi_nulls on;
create schema [SimpleUseCaseSpecification];
create table [SimpleUseCaseSpecification].[UseCases]
(
[Id] bigint not null identity,
[Goal] nvarchar(max) null,
[Name] nvarchar(max) not null,
[PrimaryActor] nvarchar(max) not null,
[SuccessScenario] nvarchar(max) not null,
constraint [PK_UseCases] primary key clustered ([Id])
);
commit transaction;
Use M Compiler to compile the M Schema into a binary format. The following command will output a file called SimpleUseCase.mx.
m.exe –t:Repository –p:Image SimpleUseCase.m
Use M Image loader to install the M image into the “Oslo” Repository.
mx.exe /i:SimpleUseCase.mx /db:Repository
At this point, you can use Microsoft SQL Server Management Studio (or any other data access tool) to validate that tables/views have been created in the Repository catalog.
Use “Oslo” Intellipad tool to create M Instance file. Save the file as SimpleUseCaseSampleInput.m. The following snippet will result in two Use Case instances – “Smoke detection” and “Disarm alarm system”. The example use case instances are taken from this PDF link available on Alistair Cockburn’s website. You can choose to include the sample instances in the same M file where the schema is defined, but it is cleaner to keep the schemas and instances separate.
// Sample Instances
UseCases
Name = "Smoke detection",
Goal = "To inform stakeholders of the fire in the house",
SuccessScenario = "1> One of the smoke detector signals smoke presence. 2> System identifies smoke detector location by its comm. port. 3> System informs stakeholders via phone line and the a/v speaker.",
PrimaryActor = "Smoke detector"
},
Name = "Disarm alarm system",
Goal = "Quick, safe and straight forward disarming of the alarm system.",
SuccessScenario = "1> User enters 4 digit password. 2> System recognizes password and disarms. 3> User disables alarm system.",
PrimaryActor = "House Owner"
The M instances correspond to the following T-SQL statements.
insert into [SimpleUseCaseSpecification].[UseCases] ([Name], [Goal], [SuccessScenario], [PrimaryActor])
values (N'Smoke detection', N'To inform stakeholders of the fire in the house', N'1> One of the smoke detector signals smoke presence. 2> System identifies smoke detector location by its comm. port. 3> System informs stakeholders via phone line and the a/v speaker.', N'Smoke detector')
;
values (N'Disarm alarm system', N'Quick, safe and straight forward disarming of the alarm system.', N'1> User enters 4 digit password. 2> System recognizes password and disarms. 3> User disables alarm system.', N'House Owner')
To compile the model instances into an “M” binary image -
m -t:Repository -p:Image -r:SimpleUseCase.mx SimpleUseCaseSampleInput.m
To insert the use case model instances into the repository, run the following utility. On success, you should be able to use your favorite database access technology to explore data in the repository. Of course, if you had access to Quadrant, I would have recommended you to use Quadrant to browse this data.
mx /i:SimpleUseCaseSampleInput.mx /db:Repository /r:SimpleUseCase.mx
For the simple Use Case schema, as a starter, let’s use the following simple language. We will later evolve this to match most of Use Case Template by Alistair Cockburn. The file will contain 1 or more use cases, delimited by “*”. Each use case has a field name label followed by a text literal. Save the file as SimpleUseCaseInput.uc.
Use Case: "Activate perimeter monitoring"
Goal in Context: "To have one part of the house monitored for alarm conditions, and other part available for use."
Primary Actor: "Home Owner"
Main Success Scenario: "Actor selects to set perimeter area monitoring. System activates."
*
The grammar is written such so that the output productions result in a M Instance graph very similar to one created by hand in Step 2a.
import CommonLanguages { Common as C };
language SimpleUseCaseLanguage
interleave WhiteSpacing = C.Space | C.LF | C.CR;
// Entry point
syntax Main =
uclist:C.List(UseCaseDecl)
=> UseCases { valuesof(uclist) };
// One Use Case
syntax UseCaseDecl =
u:UseCaseNameDecl
g:GoalDecl
a:ActorDecl
s:MainScenarioDecl
EndUseCaseDecl
=> { u, g, a, s };
syntax UseCaseNameDecl =
kwUseCase n:C.Text
=> Name {n};
syntax GoalDecl =
kwGoal goal:C.Text
=> Goal {goal};
syntax ActorDecl =
kwActor n:C.Text
=> PrimaryActor {n};
syntax MainScenarioDecl =
kwMainScenario sc:C.Text
=> SuccessScenario { sc };
syntax EndUseCaseDecl = "*";
// Tokens
@{Classification["Keyword"]} token kwUseCase = "Use Case: ";
@{Classification["Keyword"]} token kwGoal = "Goal in Context: ";
@{Classification["Keyword"]} token kwActor = "Primary Actor: ";
@{Classification["Keyword"]} token kwMainScenario = "Main Success Scenario: ";
module CommonLanguages
export Common;
language Common
// Parameterized List rule
syntax List(element)
= item:element => [item]
| item:element list:List(element) => [item, valuesof(list)];
syntax List(element, separator)
| item:element separator list:List(element, separator) => [item, valuesof(list)];
// Whitespace
syntax LF = "\u000A";
syntax CR = "\u000D";
syntax Space = "\u0020";
syntax Whitespace = LF | CR | Space;
syntax NewLine = LF | CR;
token Text = ( 'A'..'Z' | 'a'..'z' | TSpecialChar | TNumber )+;
token TSpecialChar = ('.' | ',' | '$' | '%' | '#' | '>' | '<' | '/' | '\\' | '\t' | TSpace )+;
token TSpace = ' ';
token TNumber = ( '0'..'9' )+;
The above listed grammar generates the following M graph for the given input. You can use Intellipad’s MGrammar Mode -> Tree Preview to see the input, grammar and output in three panes side by side.
Here is the M Graph output.
UseCases{
Name{
"Activate perimeter monitoring"
Goal{
"To have one part of the house monitored for alarm conditions, and other part available for use."
PrimaryActor{
"Home Owner"
SuccessScenario{
"Actor selects to set perimeter area monitoring. System activates."
Compile the M Grammar file. The following command should output SimpleUseCase.mgx.
mg SimpleUseCase.mg
Use the binary M Grammar image file as a reference to work with the sample textual DSL input and generate M instance graph. The following command should output SimpleUseCaseInput.m.
mgx –r:SimpleUseCase.mgx –MmoduleName:SimpleUseCaseSpecification SimpleUseCaseInput.uc
Compile the M Instance definition into a binary M Image.
m -t:Repository -p:Image -r:SimpleUseCase.mx SimpleUseCaseInput.m
Load the M Instance binary image into the Repository.
mx /i:SimpleUseCaseInput.mx /db:Repository /r:SimpleUseCase.mx
If we start Quadrant after performing each individual step, we should expect to see the following:
· After Step 1, Quadrant should be able to recognize that a new domain ‘Use Case’ is available in the repository
o Click on EXPLORER -> Browse All and scroll down to find SimpleUseCaseSpecification module shown in the second pane
o Selecting the module should show available extents in the third pane, in our case, UseCases
· After Step 2, Wendy should be able to browse/edit the sample instances of the Use Cases available in the repository
o Click on EXPLORER -> Browse All
o Select SimpleUseCaseSpecifications in second pane
o Select UseCases in third pane; the use case instances are shown in the 4th pane.
· After Step 3, Wendy should be able to browse/edit the sample instance created using Custom Textual DSL
Let's see what the Step 4 entails in order for Wendy to create/edit/delete instances using different data visualizations.
Start Quadrant and navigate to Use Case model in the Repository Explorer.
If you drag UseCases from Repository Explorer onto the Quadrant workspace, by default, it will open it in a Workpad using a “List” editor. You can use ribbon tab UseCases -> Switch View to switch to one of other out-of-box editors such as “Table”. See the picture below with 4 open workpads:
· Workpad showing a collection of available Use Case instances in the repository using out-of-box List editor
· Workpad showing a collection of available Use Case instances in the repository using out-of-box Table editor
· Workpad showing single instance of Use Case (“Disarm alarm system”) using out-of-box Property Sheet editor
· Workpad showing single instance of Use Case (“Activate perimeter monitoring”) using out-of-box Diagram Mster/Detail editor
a. Extend Quadrant
The Quadrant editor configurations are themselves modeled and stored in the repository. So how can ISVs and SIs create instances of these “Quadrant” models:
· Use database access technology of choice – EDM, ADO.NET, etc.
· Use Quadrant (this even gives Wendy some degree of freedom to customize the end-user experience for her own needs). My guess is most ISVs would prefer to do these customizations using a “batch file” like approach. This is where M Grammar again comes in handy. In the limit (not V1), Microsoft will provide some variation of textual editor DSL to make it easier for ISVs to share, version and create custom editor configurations for their domains.
For the simple use case domain, I have done the following customizations using Quadrant itself.
· Change the icon associated with UseCases schema
o Model: Quadrant.SchemaExtension.ViewerHints
o Property: StaticEntityIcon (Quadrant.SchemaExtension.EntityIcon)
· Change the display name for single Use Case instance – by default the database identifier is used
o Property: DescriptionProperty
· Set “Table” as the preferred editor for working with collection of Use Cases
o Model: Quadrant.Defaults.PreferredViewersForEntityTypes
o Property: Collection
After the customizations, the four use case workpads use the “Name” of the use case as the display name and make use of the custom icon.
Wendy can now use base functionality provided by Quadrant to create new and edit/delete existing Use Case instances. See screencast [here].
Complete Example
Let’s evolve the Use Case domain model to be a little more complex that what we have defined so far. You can use instructions provided in Steps 1 – 3 as appropriate with the revised example to load the model schema and/or model instances into the repository.
Revised M Schema and M Instances -- UseCase.m
module UseCaseSpecification
export Actor, Actors;
export SystemBoundary, SystemBoundaries;
export Association, Associations;
// **** Extents
Actors : Actor* where
item.RelatesToSystemBoundary in SystemBoundaries;
UseCases : UseCase* where
item.PrimaryActor in Actors &&
item.SuccessScenario in UseCaseScenarios &&
item.DefinedInSystemBoundary in SystemBoundaries &&
item.RelatedInformation in RelatedInformation;
SystemBoundaries : SystemBoundary*;
UseCaseScenarios : UseCaseScenario*;
RelatedInformation : RelatedInfoType*;
Associations : Association* where
item.From in UseCases &&
item.To in UseCases;
// **** Types
type UMLItem
Description : Text?;
// A human user or external system with which the
// system interacts.
type Actor : UMLItem
RelatesToSystemBoundary : SystemBoundary?;
// Functional requirement described from the
// perspective of the users of a system
type UseCase : UMLItem
DefinedInSystemBoundary : SystemBoundary?;
Scope : Text?;
Preconditions : Text?;
SuccessEndCondition : Text?;
FailedEndCondition : Text?;
PrimaryActor : Actor?;
Trigger : Text?;
SuccessScenario : UseCaseScenario?;
RelatedInformation : RelatedInfoType?;
type RelatedInfoType
Priority : Text? where value in { "High", "Medium", "Low" };
PerformanceTarget : Text?;
Frequency : Text?;
// Success Scenario for a UseCase
type UseCaseScenario : UMLItem
Steps : Text*;
type SystemBoundary : UMLItem;
// Specifies relationship between two use cases
type Association : UMLItem
From : UseCase?;
To : UseCase?;
Kind : AssociationKind = "Include";
type AssociationKind { "Include", "Extend", "Generalization" }
// *** Sample Instances
Scope = "Alarm System",
Preconditions = "Alarm system is armed and active. Detector is working. Communication means are functioning.",
SuccessEndCondition = "Stakeholder is informed",
FailedEndCondition = "Stakeholder are not informed of smoke. Fire destroys monitored property.",
PrimaryActor =
Name = "Smoke Detector"
Trigger = "Detection of smoke",
DefinedInSystemBoundary =
Name = "Alarm System"
SuccessScenario =
Name = "Steps for smoke detection",
Steps =
{ "One of the smoke detector signals smoke presence." },
{ "System identifies smoke detector location by its comm. port." },
{ "System informs stakeholders via phone line and the a/v speaker." }
RelatedInformation =
Priority = "High",
PerformanceTarget = "Stakeholders should be notified within 5 seconds",
Frequency = "Rarely. Only in extreme cases of fire, or strong smoke concentration."
Scope = "Authentication and system enabling",
Preconditions = "Alarm system is armed and active. User knows disarming procedure and remembers password.",
SuccessEndCondition = "System is disarmed",
FailedEndCondition = "System goes off",
Name = "House Owner"
Trigger = "Entry of numerical password sequence",
Name = "Steps for disarming alarm system",
{ "User enters 4 digit password" },
{ "System recognizes password and disarms" },
{ "User disables alarm system" }
PerformanceTarget = "Disarming process has to occur within 15 seconds.",
Frequency = "At least 3 times a day. Every time user leaves the house."
Revised textual use case template -- UseCaseInput.uc
Use Case: Smoke detection
CHARACTERISTIC INFORMATION
Goal in Context: To inform stakeholders of the fire in the house
Scope: Alarm System
Preconditions: Alarm system is armed and active. Detector is working. Communication means are functioning.
Success End Condition: Stakeholder is informed
Error End Condition: Stakeholder are not informed of smoke. Fire destroys monitored property.
Primary Actor: Smoke Detector
Trigger: Detection of smoke
MAIN SUCCESS SCENARIO
Step#1 One of the smoke detector signals smoke presence.
Step#2 System identifies smoke detector location by its commport.
Step#3 System informs stakeholders via phone line and the a/v speaker.
RELATED INFORMATION
Priority: High
Performance Target: Stakeholders should be notified within 5 seconds.
Frequency: Rarely. Only in extreme cases of fire, or strong smoke concentration.
SCHEDULE
Due Date: Jan/12/1000
Use Case: Disarm alarm system
Goal in Context: Quick, safe and straight forward disarming of the alarm system.
Scope: Authentication and system enabling
Preconditions: Alarm system is armed and active. User knows disarming procedure and remembers password.
Success End Condition: System is disarmed
Error End Condition: System goes off
Primary Actor: House Owner
Trigger: Entry of numerical password sequence
Step#1 User enters 4 digit password
Step#2 System recognizes password and disarms
Step#3 User disables alarm system
Performance Target: Disarming process has to occur within 15 seconds.
Frequency: At least 3 times a day. Every time user leaves the house.
Revised M Grammar -- UseCase.mg
language UseCaseLanguage
// Ignore whitespace
interleave Skippable = C.LF | C.CR | C.Space;
t:UseCaseDocument+
=> UseCases { valuesof(t) };
syntax UseCaseDocument =
kwUseCase ucn:C.Text
kwCharInfo?
cinfo:CharInfoSubSectionDecl?
sl:C.List(SectionDecl)
=> { Name {ucn}, valuesof(cinfo), valuesof(sl) };
// Each section in the use case
syntax SectionDecl =
section:MainScenarioSectionDecl => section
| section:SubVariationsSectionDecl => section
| section:RelatedInfoSectionDecl => section
| section:OpenIssuesSectionDecl => section
| section:ScheduleSectionDecl => section;
// CHARACTERISTIC INFORMATION SECTION
syntax CharInfoSectionDecl =
sl:C.List(CharInfoSubSectionDecl)
=> { valuesof(sl) };
syntax CharInfoSubSectionDecl =
(g:GoalDecl => g)
(s:ScopeDecl => s)
(p:PreconditionsDecl => p)
(su:SuccessEndCondDecl => su)
(e:ErrorEndCondDecl => e)
(a:ActorDecl => a)
(t:TriggerDecl => t);
kwGoal n:C.Text
=> Goal {n};
syntax ScopeDecl =
kwScope n:C.Text
=> Scope {n};
syntax PreconditionsDecl =
kwPreconditions n:C.Text
=> Preconditions {n};
syntax SuccessEndCondDecl =
kwSuccessEndCond n:C.Text
=> SuccessEndCondition {n};
syntax ErrorEndCondDecl =
kwErrorEndCond n:C.Text
=> FailedEndCondition {n};
=> PrimaryActor { Name {n} };
syntax TriggerDecl =
kwTrigger n:C.Text
=> Trigger {n};
// MAIN SUCCESS SCENARIO SECTION
syntax MainScenarioSectionDecl =
kwMainScenario
sl:C.List(ScenarioStepDecl)?
=> SuccessScenario { Steps { valuesof(sl) } };
syntax ScenarioStepDecl =
"Step#" i:C.TNumber n:C.Text
=> {n};
// SUB-VARIATIONS SECTION
syntax SubVariationsSectionDecl =
kwSubVariations
des:C.Text?
vl:C.List(SubVariationDecl)?
=> Variations { valuesof(vl) };
syntax SubVariationDecl =
"Variation#" i:C.TNumber n:C.Text
=> Variation {n};
// RELATED INFORMATION SECTION
syntax RelatedInfoSectionDecl =
kwRelatedInfo
pri:PriorityDecl?
perf:PerfTargetDecl?
freq:FreqDecl?
=> RelatedInformation { pri, perf, freq };
syntax PriorityDecl =
kwPriority n:PriorityEnum
=> Priority {n};
syntax PriorityEnum =
n:"High" => n
| n:"Medium" => n
| n:"Low" => n;
syntax PerfTargetDecl =
kwPerfTarget n:C.Text
=> PerformanceTarget {n};
syntax FreqDecl =
kwFreq n:C.Text
=> Frequency {n};
// OPEN ISSUES SECTION
syntax OpenIssuesSectionDecl =
kwOpenIssues
il:C.List(IssueDecl)
=> Issues {valuesof(il)};
syntax IssueDecl =
"Issue#" i:C.TNumber n:C.Text
=> Issue {n};
// SCHEDULE
syntax ScheduleSectionDecl =
kwSchedule
kwDueDate date:C.TDate
=> DueDate {date};
// Common
token kwCharInfo = "CHARACTERISTIC INFORMATION";
token kwMainScenario = "MAIN SUCCESS SCENARIO";
token kwSubVariations = "SUB-VARIATIONS";
token kwRelatedInfo = "RELATED INFORMATION";
token kwOpenIssues = "OPEN ISSUES";
token kwSchedule = "SCHEDULE";
// Characteristic Information
token kwGoal = "Goal in Context: ";
token kwScope = "Scope: ";
token kwPreconditions = "Preconditions: ";
token kwActor = "Primary Actor: ";
token kwTrigger = "Trigger: ";
token kwSuccessEndCond = "Success End Condition: ";
token kwErrorEndCond = "Error End Condition: ";
// Main Success Scenario
// Sub Variations
// Related Information
token kwPriority = "Priority: ";
token kwPerfTarget = "Performance Target: ";
token kwFreq = "Frequency: ";
token kwDueDate = "Due Date: ";
token TDate = TMonth "/" TNumber "/" TYear;
token TMonth = ("Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec" );
token TYear = ('1'..'3') ('0'..'9') ('0'..'9') ('0'..'9');
token Text = ('A'..'Z' | 'a'..'z' | ' ') ( 'A'..'Z' | 'a'..'z' | TSpecialChar | TNumber )+;
token TSpecialChar = ('.' | ',' | '$' | '%' | '>' | '<' | '/' | '\\' | '\t' | TSpace )+;
token TSpace = ' '+;
token TNumber = '0'..'9'+;
// token Text = ('a'..'z' | 'A'..'Z') ('a'..'z' | TSpecialChar | TNumber | 'A'..'Z' | ' ' | '\t')* ('a'..'z' | 'A'..'Z' | '.');
token TComma = ",";
token TSemiColon = ";";
To summarize, we created a new model using “M” Schema and installed it in the “Oslo” Repository using “M” tool chain. We also created a textual DSL using “M” Grammar and customized “Quadrant” Visual Modeling Tool to enable end-users to populate Repository with the model instances.
Download the source M and MG files used in the attached MUseCaseSample.zip.