Generating a MEAN-style web application from models (T plus 1 days)

This is the last installment to a series started about two weeks ago. If you remember, I set off to build a code generator that could produce 100% of the code for a business application targeting the MEAN (for Mongo, Express, Node.js and Angular) stack. I am writing this a day after the presentation took place.

So, the big day finally arrived. Unfortunately, the presentation was not one of my best. If my delivery was not that great, to make matters worse, turns out my idea of exposing MDD (with Javascript as target platform) to a crowd seeking wisdom on Javascript didn’t work very well, people didn’t seem be interested in modeling and code generation at all. Something to take into account in the future. Anyways, the slides (in Portuguese) appear below this post.

Also, the code generator was not complete (was anyone else surprised?), so I couldn’t show code running for many features, and instead had to show the generated code (that looked almost right but not quite there yet). I guess that contributed to a less interesting presentation.

On the bright side, a lot of progress on the code generator was made. Take a look at the latest state of the generated code. I am quite proud of what is there now. But there is still more progress to be made until at least the sample applications I have all translate to feature complete and correct MEAN applications.

You will hear more about that here in the near future. Stay tuned.

EmailFacebookLinkedInGoogle+Twitter

Generating a MEAN-style web application from models (T minus 3 days)

This is another installment to a short series started about 10 days ago. If you remember, I am building a code generator that can produce 100% of the code for a business application targeting the MEAN (for Mongo, Express, Node.js and Angular) stack.

What happened since the last update

Since the previous post, a lot of progress has been made. Much of the code for a domain model class is being generated (including state machine support), as you can see below (bear in mind there are still quite a few gaps – but I still have 3 days!):

    var EventEmitter = require('events').EventEmitter;        

    /**
     * An issue describes a problem report, a feature request or just a work item for a project. Issues are reported by and
     * assigned to users, and go through a lifecycle from the time they are opened until they are resolved and eventually
     * closed. 
     */
    var issueSchema = new Schema({
        summary : String,
        issueId : Number,
        issueKey : String,
        reportedOn : Date,
        severity : Severity,
        status : Status,
        resolution : Resolution,
        resolvedOn : Date,
        votes : Number,
        commentCount : Number,
        waitingFor : String,
        mine : Boolean,
        free : Boolean,
        description : String
    });
    
    /*************************** ACTIONS ***************************/
    
    /**
     *  Report a new issue. 
     */
    issueSchema.statics.reportIssue = function (project, summary, description, severity) {
        newIssue = new Issue();
        newIssue.summary = summary;
        newIssue.description = description;
        newIssue.severity = severity;
        newIssue.reporter = User.current;
        newIssue.project = project;
        newIssue.userNotifier.issueReported(newIssue.issueKey, summary, description, newIssue.reporter.email);
    };
    
    /**
     *  Release the issue so another committer can work on it. 
     */
    issueSchema.methods.release = function () {
        this.assignee = null;
    };
    
    /**
     *  Assign an issue to a user. 
     */
    issueSchema.methods.assign = function (newAssignee) {
        this.assignee = newAssignee;
    };
    
    /**
     *  Suspend work on this issue. 
     */
    issueSchema.methods.suspend = function () {};
    
    /**
     *  Start/resume work on this issue. 
     */
    issueSchema.methods.start = function () {};
    
    /**
     *  Resolve the issue. 
     */
    issueSchema.methods.resolve = function (resolution) {
        this.resolvedOn = new Date();
        this.resolution = resolution;
    };
    
    /**
     *  Reopen the issue. 
     */
    issueSchema.methods.reopen = function (reason) {
        this.resolvedOn = null;
        this.resolution = null;
        if (reason.notEquals("")) {
            this.comment(reason);
        }
    };
    
    /**
     *  Add a comment to the issue 
     */
    issueSchema.methods.comment = function (text) {
        this.addComment(text, null);
    };
    
    issueSchema.methods.addWatcher = function (userToAdd) {
        this.issuesWatched = userToAdd;
    };
    
    issueSchema.methods.vote = function () {
        this.voted = User.current;
    };
    
    issueSchema.methods.withdrawVote = function () {
        delete this.voted;
    };
    
    /**
     *  Take over an issue currently available. 
     */
    issueSchema.methods.assignToMe = function () {
        this.assignee = User.current;
    };
    
    /**
     *  Take over an issue currently assigned to another user (not in progress). 
     */
    issueSchema.methods.steal = function () {
        this.assignee = User.current;
    };
    
    /**
     *  Close the issue marking it as verified. 
     */
    issueSchema.methods.verify = function () {
    };
    /*************************** QUERIES ***************************/
    
    issueSchema.statics.bySeverity = function (toMatch) {
        return this.model('Issue').find().where('severity').eq(toMatch).exec();
    };
    
    issueSchema.statics.byStatus = function (toMatch) {
        return Issue.filterByStatus(this.model('Issue').find(), toMatch).exec();
    };
    /*************************** DERIVED PROPERTIES ****************/
    
    
    issueSchema.methods.getIssueKey = function () {
        return this.project.token + "-" + this.issueId;
    };
    
    issueSchema.methods.getVotes = function () {
        return  ;
    };
    
    issueSchema.methods.getCommentCount = function () {
        return  ;
    };
    
    issueSchema.methods.getWaitingFor = function () {
        return "" +  + " day(s)";
    };
    
    issueSchema.methods.getMine = function () {
        return User.current == this.assignee;
    };
    
    issueSchema.methods.getFree = function () {
        return this.assignee == null;
    };
    /*************************** PRIVATE OPS ***********************/
    
    issueSchema.methods.referenceDate = function () {
        if (this.resolvedOn == null) {
            return new Date();
        } else  {
            return this.resolvedOn;
        }
    };
    
    issueSchema.statics.filterByStatus = function (issues, toMatch) {
        return issues.where('status').eq(toMatch);
    };
    
    issueSchema.methods.addComment = function (text, inReplyTo) {
        comment = new Comment();
        comment.user = User.current;
        comment.on = new Date();
        comment.commented = text;
        comment.inReplyTo = inReplyTo;
        this.issue = comment;
        this.userNotifier.commentAdded(this.issueKey, comment.user.email, this.reporter.email, text);
    };
    /*************************** STATE MACHINE ********************/
    Issue.emitter.on('resolve', function () {
        if (this.status == 'Open') {
            this.status = 'Resolved';
            return;
        }
        if (this.status == 'Assigned') {
            this.status = 'Resolved';
            return;
        }
    });     
    
    Issue.emitter.on('assignToMe', function () {
        if (this.status == 'Open') {
            this.status = 'Assigned';
            return;
        }
    });     
    
    Issue.emitter.on('assign', function () {
        if (this.status == 'Open') {
            this.status = 'Assigned';
            return;
        }
    });     
    
    Issue.emitter.on('suspend', function () {
        if (this.status == 'InProgress') {
            this.status = 'Assigned';
            return;
        }
    });     
    
    Issue.emitter.on('release', function () {
        if (this.status == 'Assigned') {
            this.status = 'Open';
            return;
        }
    });     
    
    Issue.emitter.on('steal', function () {
        if (this.status == 'Assigned') {
            this.status = 'Assigned';
            return;
        }
    });     
    
    Issue.emitter.on('start', function () {
        if (this.status == 'Assigned') {
            this.status = 'InProgress';
            return;
        }
    });     
    
    Issue.emitter.on('verify', function () {
        if (this.status == 'Resolved') {
            this.status = 'Verified';
            return;
        }
    });     
    
    Issue.emitter.on('reopen', function () {
        if (this.status == 'Verified') {
            this.status = 'Open';
            return;
        }
    });     
    
    var Issue = mongoose.model('Issue', issueSchema);
    Issue.emitter = new EventEmitter();

This is just for one of the entity classes defined in the ShipIt example application, here is an excerpt of the original model that covers the Issue entity:

(* 
    An issue describes a problem report, a feature request or just a work item for a project.
    Issues are reported by and assigned to users, and go through a lifecycle from the time 
    they are opened until they are resolved and eventually closed.
*)
class Issue

    attribute summary : String;

    derived id attribute issueId : Integer;

    derived attribute issueKey : String := {
        self.project.token + "-" + self.issueId
    };

    attribute labels : Label[*];

    attribute project : Project;

    port userNotifier : UserNotifier;

    readonly attribute reportedOn : Date := { Date#today() };

    readonly attribute reporter : User;

    readonly attribute assignee : User[0, 1];

    attribute severity : Severity := Major;

    attribute status : Status;

    readonly attribute resolution : Resolution[0, 1];

    readonly attribute resolvedOn : Date[0, 1];

    readonly attribute comments : Comment[*];

    attribute watchers : User[*];

    derived attribute votes : Integer := {
        self<-VotedIssues->voters.size()
    };

    derived attribute commentCount : Integer := { self.comments.size() };

    derived attribute waitingFor : String := {
        "" + self.reportedOn.differenceInDays(self.referenceDate()) + " day(s)"
    };

    private derived attribute mine : Boolean := {
        User#current == self.assignee
    };

    private derived attribute free : Boolean := { self.assignee == null };

    private query referenceDate() : Date;
    begin
        if (self.resolvedOn == null) then
            return Date#today()
        else
            return self.resolvedOn;
    end;

    attribute description : Memo;

    (* Report a new issue. *)
    static operation reportIssue(project : Project, summary : String, description : Memo, 
            severity : Severity := Normal)
        precondition Must_be_logged_in { User#provisioned };
    begin
        var newIssue;
        newIssue := new Issue;
        newIssue.summary := summary;
        newIssue.description := description;
        newIssue.severity := severity;
        newIssue.reporter := User#current;
        newIssue.project := project;
        send IssueReported(
            issueKey := newIssue.issueKey, 
            summary := summary, 
            description := description, 
            userEmail := newIssue.reporter.email) to newIssue.userNotifier;
    end;

    static query bySeverity(toMatch : Severity) : Issue[*];
    begin
        return Issue extent.select((i : Issue) : Boolean {
            i.severity == toMatch
        });
    end;

    private static query filterByStatus(issues : Issue[*], toMatch : Status) : Issue[*];
    begin
        return issues.select((issue : Issue) : Boolean {
            issue.status == toMatch
        });
    end;

    static query byStatus(toMatch : Status) : Issue[*];
    begin
        return Issue#filterByStatus(Issue extent, toMatch);
    end;

    (* Release the issue so another committer can work on it. *)
    operation release()
        precondition { self.mine };
    begin
        self.assignee := null;
    end;

    (* Assign an issue to a user. *)
    operation assign(newAssignee : User)
        precondition { self.mine or self.free };
    begin
        self.assignee := newAssignee;
    end;

    (* Suspend work on this issue. *)
    operation suspend()
        precondition { self.mine };

    (* Start/resume work on this issue. *)
    operation start()
        precondition { self.mine };

    (* Resolve the issue. *)
    operation resolve(resolution : Resolution := Fixed)
        precondition { self.mine or self.free };
    begin
        self.resolvedOn := Date#today();
        self.resolution := resolution;
    end;

    (* Reopen the issue. *)
    operation reopen(reason : Memo);
    begin
        self.resolvedOn := null;
        self.resolution := null;
        if (reason != "") then
            self.comment(reason);
    end;

    (* Add a comment to the issue *)
    operation comment(text : Memo);
    begin
        self.addComment(text, null);
    end;

    private operation addComment(text : Memo, inReplyTo : Comment);
    begin
        var comment;
        comment := new Comment;
        comment.user := User#current;
        comment.\on := Date#today();
        comment.commented := text;
        comment.inReplyTo := inReplyTo;
        link IssueComments(issue := self, comments := comment);
        send CommentAdded(
            issueKey := self.issueKey, 
            author := comment.user.email, 
            userEmail := self.reporter.email, 
            comment := text) to self.userNotifier;
    end;

    operation addWatcher(userToAdd : User);
    begin
        link WatchedIssues(issuesWatched := self, watchers := userToAdd);
    end;

    operation vote()
        precondition { not (User#current == null) }
        precondition { ! self.mine }
        precondition {
            ! self<-VotedIssues->voters.includes(User#current)
        };
    begin
        link VotedIssues(voted := self, voters := User#current);
    end;

    operation withdrawVote()
        precondition { not (User#current == null) }
        precondition {
            self<-VotedIssues->voters.includes(User#current)
        };
    begin
        unlink VotedIssues(voted := self, voters := User#current);
    end;

    (* Take over an issue currently available. *)
    operation assignToMe()
        precondition { User#current.committer }
        precondition { not self.mine };
    begin
        self.assignee := User#current;
    end;

    (* Take over an issue currently assigned to another user (not in progress). *)
    operation steal()
        precondition { User#current.committer }
        precondition { not (self.mine) };
    begin
        self.assignee := User#current;
    end;

    (* Close the issue marking it as verified. *)
    operation verify();
    begin
    end;

    statemachine Status

        initial state Open
            transition on call(resolve) to Resolved;
            transition on call(assignToMe) to Assigned;
            transition on call(assign) to Assigned;
        end;

        state InProgress
            transition on call(suspend) to Assigned;
        end;

        state Assigned
            transition on call(release) to Open;
            transition on call(resolve) to Resolved;
            transition on call(steal) to Assigned;
            transition on call(start) to InProgress;
        end;

        state Resolved
            transition on call(verify) to Verified;
        end;

        state Verified
            transition on call(reopen) to Open;
        end;

    end;

end;

More examples here.

Xtend is still making the difference

What an awesome language for writing code generators Xtend is. See, for example, how simple is the code to implement state machine support in Javascript, including guard conditions on transitions, and automatically triggering of on-entry and on-exit behavior:

    def generateStateMachine(StateMachine stateMachine, Class entity) {
        val stateAttribute = entity.findStateProperties.head
        if (stateAttribute == null) {
            return ''
        }
        val triggersPerEvent = stateMachine.findTriggersPerEvent
        triggersPerEvent.entrySet.map[generateEventHandler(entity, stateAttribute, it.key, it.value)].join('\n')
    }
    
    def generateEventHandler(Class entity, Property stateAttribute, Event event, List triggers) {
        val modelName = entity.name;     
        '''
        «modelName».emitter.on('«event.generateName»', function () {
            «IF (triggers.exists[(it.eContainer as Transition).guard != null])»
            var guard;
            «ENDIF»
            «triggers.map[generateHandlerForTrigger(entity, stateAttribute, it)].join('\n')»
        });     
        '''
    }
    
    def generateHandlerForTrigger(Class entity, Property stateAttribute, Trigger trigger) {
        val transition = trigger.eContainer as Transition
        val originalState = transition.source
        val targetState = transition.target
        '''
        «transition.generateComment»if (this.«stateAttribute.name» == '«originalState.name»') {
            «IF (transition.guard != null)»
            guard = «generatePredicate(transition.guard)»;
            if (guard()) {
                «generateStateTransition(stateAttribute, originalState, targetState)»
            }
            «ELSE»
            «generateStateTransition(stateAttribute, originalState, targetState)»
            «ENDIF»
        }'''
    }
    
    def generateStateTransition(Property stateAttribute, Vertex originalState, Vertex newState) {
        '''
        «IF (originalState instanceof State)»
            «IF (originalState.exit != null)»
            (function() «generateActivity(originalState.exit as Activity)»)();
            «ENDIF»
        «ENDIF»
        this.«stateAttribute.name» = '«newState.name»';
        «IF (newState instanceof State)»
            «IF (newState.entry != null)»
            (function() «generateActivity(newState.entry as Activity)»)();
            «ENDIF»
        «ENDIF»
        return;
        '''
    }
    
    def generateName(Event e) {
        switch (e) {
            CallEvent : e.operation.name
            SignalEvent : e.signal.name
            TimeEvent : '_time'
            AnyReceiveEvent : '_any'
            default : unsupportedElement(e)
        }
    }

Next steps

Sorry if I am being a bit too terse. Will be glad to reply to any questions or comments, but time is running out fast and there is lots to be done still. For the next update, I hope to have Express route generation for the REST API and hopefully will have started on test suite generation. Let’s see how that goes!

EmailFacebookLinkedInGoogle+Twitter

Command Query Separation in TextUML

Ever heard of Command Query Separation? It was introduced by Bertrand Meyer and implemented in Eiffel. But I will let Martin Fowler explain:

The term ‘command query separation’ was coined by Bertrand Meyer in his book “Object Oriented Software Construction” – a book that is one of the most influential OO books during the early days of OO. [...]

The fundamental idea is that we should divide an object’s methods into two sharply separated categories:

  • Queries: Return a result and do not change the observable state of the system (are free of side effects).
  • Commands: Change the state of a system but do not return a value.

Query operations in UML

UML too allows an operation to be marked as a query. The section on Operations in the UML specification states:

If the isQuery property is true, an invocation of the Operation shall not modify the state of the instance or any other element in the model.

Query operations in TextUML

The next release of TextUML (which runs in Cloudfier today) will start exposing the ability to mark an operation as a query operation. Being just a notation for UML, the same definition of the UML spec applies to TextUML operations marked as queries.

But how do you mark an operation as a query in TextUML, you ask? You use the query keyword instead of the usual operation keyword (it is not just a modifier, it is a replacement for the usual keyword):

query totalExpenses(toSum : Expense[*]) : Double;

The TextUML compiler imposes a few rules when it sees a query operation:

  • it will require the operation to have a return value
  • it won’t let the operation perform any actions that could have side effects, such as creating or destroying objects, writing properties or linking objects, or invoke any other non-query operations
  • also, it will only let you invoke operations from a property derivation if they are query operations

Example of a query operation


    private query totalExpenses(toSum : Expense[*]) : Double;
    begin
        return (toSum.reduce((e : Expense, sum : Double) : Double {
            sum + e.amount
        }, 0) as Double);
    end;

Example of a derived attribute using a query operation


    derived attribute totalRecorded : Double := {
        self.totalExpenses(self.recordedExpenses)
    };

But why is Command Query Separation a good thing?

By allowing a modeler/programmer to explicitly state whether an operation has side effects allows a compiler or runtime to take advantage of the guarantee of lack of side effects to do things such as reorder invocations, cache results, safely reissue in case of failure which can improve performance and reliability.

EmailFacebookLinkedInGoogle+Twitter

Checking the current state of a UML state machine

In Cloudfier, we use UML as the core language for building business applications. UML is usually well-equipped for general purpose business domain-centric application modeling, but that doesn’t mean it always does everything needed out of the box.

Case at hand: assuming one is developing an expense reporting application and modeled an expense’s status as a state machine (in TextUML):

class Expense
    /* ... */
    attribute status : Status;
    operation review();
    operation approve();
    operation reject();
    operation submit();

    statemachine Status
        initial state Draft
            transition on call(submit) to Submitted;
        state Submitted
            transition on call(approve) to Approved
            transition on call(reject) to Rejected
            transition on call(review) to Draft;
        terminate state Approved;
        terminate state Rejected;        
    end;
end;

How do you model the following in UML?

Show me all expenses that are waiting for approval.

Turns out there is no support in UML for reasoning based on the current state of a state machine.

Creative modeling

So, what do you do when UML does not have a language element that you need? You extend it, in our case, using a stereotype applicable to the LiteralNull metaclass (in TextUML):

stereotype VertexLiteral extends LiteralNull
    property vertex : Vertex;
end;

So, a vertex literal is a value specification, more specifically, a variant of LiteralNull, that can refer to a Vertex, which is a metaclass that represents the states (including pseudo-states) in a state machine.

Notation, notation

In terms of notation, I chose to make State/Vertex literals look like enumeration literals: Status#Approved or Status#Draft. So, back to the original question, this is how you could model a query that returns all expenses that are in the Submitted state:

    static operation findAllSubmitted() : Expense[*];
    begin 
        return Expense extent.select ((e : Expense) : Boolean {
            return e.status == Status#Submitted
        });
    end;

If you are thinking to yourself: I didn’t know UML had queries or closures!?, well, it usually doesn’t. See the posts on SQL queries in UML and Closures in UML for some background on this.

Note also that if you wanted to refer to the symbol Status from a class different than the one enclosing it you will need to qualify it (i.e. Expense::Status#Submitted).

Show me more!

You can run the Expenses application showing state machines and state-based queries in Cloudfier right now (login is “guest” or any of the employee names you will see later).

The entire Expenses sample application (currently 150 lines of generously spaced TextUML) is available on BitBucket. You can also easily check it out into Cloudfier so you can run your own copy of the application on the web (there is nothing to install). Give it a try!

What do you think?

Your feedback (questions, support or criticism) to any of the ideas presented in this post is very welcome.

UPDATE: I started a thread on the subject on the UML Forum group, and turns out you can do this kind of reasoning in OCL, but indeed, not in UML itself. Well, now you can.

EmailFacebookLinkedInGoogle+Twitter

Yet another Orion-based site: cloudfier.com

Okay, we are live.

I just put the last finishing touches on the developer site at cloudfier.com.

The developer site, develop.cloudfier.com, is powered by Orion. Cloudfier’s instance of Orion has several features to support modeling with TextUML, such as:

  • Syntax highlighting
  • Outline
  • Validation
  • Auto-formatting
  • Templates

and I have a picture to prove it:

but wouldn’t you rather see for yourself? If you are shy because you don’t know how to model in TextUML, just make sure you create a file with a “.tuml” extension and use the content assist templates to get a model going. Or if you are feeling lazy, just clone this Git repository: https://bitbucket.org/abstratt/cloudfier-examples.git

But what and who is Cloudfier for you may ask. I won’t tell you here though. Please go to cloudfier.com, give it a quick read. If you don’t get it, please let me know in the comments – a main goal now is to ensure the main page can get the message across.

EmailFacebookLinkedInGoogle+Twitter

TextUML Toolkit finally gets continuous integration thanks to Tycho and CloudBees

TextUML Toolkit 1.8 is now available! You can install it as usual using http://abstratt.com/update as the update site. There is also a snapshot update site, which will work from within Eclipse only:

jar:https://repository-textuml.forge.cloudbees.com/snapshot/com/abstratt/mdd/com.abstratt.mdd.oss.repository/1.0/com.abstratt.mdd.oss.repository-1.0.zip!/

This is a transition release where the TextUML Toolkit now uses continuous integration for producing builds via Eclipse Tycho, as opposed to developer initiated builds from the IDE. This benefits contributors (the development setup is much simpler), but primarily users – since it is now so much easier to obtain the source code and generate a release users can expect much more frequent releases, and hopefuly more goodies from occasional contributors.

Talking about frequent releases, if you don’t mind living on the bleeding edge, I invite you to install the TextUML Toolkit from the snapshot update site (that is what you get if you install the Toolkit using the Eclipse Marketplace Client). That way, features or fixes will become available to you a day after they have been committed.

This release contains a number of new features and bug fixes added since 1.7 was released a year ago, but we are not documenting those yet. You will see those properly promoted in a future release. Our focus now was to get our release engineering act straight, and I think we succeeded, thanks to Tycho.

Finally, we would like to thank CloudBees for their generous free plan that allows us to set up Jenkins continuous builds for the TextUML Toolkit at no cost. On that note, we are applying for a FOSS plan so we can have our build results available for everyone to see, and as a bonus, enjoy a slightly higher monthly build quota. As you can see, we are already living up to our side of the deal by spreading the word about their cool DEV@cloud product. :)

UPDATE: CloudBees is now providing the TextUML Toolkit project with a free DEV@cloud instance.

EmailFacebookLinkedInGoogle+Twitter

Adding State Machines to TextUML and AlphaSimple [take 1]

I decided to go ahead and finally implement support for state machines in TextUML and AlphaSimple.

This is an example of what a state machine will look like (take 1), based on fig. 15.33 in the UML specification 2.4:


(...)
statemachine Phone

  initial state
    entry { self.startDialTone() }
    exit { self.stopDialTone() }
    transition on digit to PartialDial;

  state PartialDial
    transition on digit to PartialDial
    transition when { self.numberIsValid() } to Completed;

  final state Completed;

end;
(...)

A state machine may declare multiple states. Each state declares a number of transitions to other states. Each transition may be triggered by many events (or none), each denoted by the keyword ‘on’, and may optionally present a guard constraint (using the keyword ‘when’). The initial state is the only one that may remain unnamed. The final state cannot have outgoing transitions, but just like any other state, it may declare entry/exit behaviors.

What do you think? I did try to find existing textual notations for UML, like this and this, but none of those seem to be documented or look like covering all the UML features I want to support. Any other pointers?

EmailFacebookLinkedInGoogle+Twitter

Feedback wanted: invariant constraints in AlphaSimple/TextUML

I am working on support for invariant constraints in AlphaSimple/TextUML.

Some of the basic support has already made into the live site. For instance, the AlphaSimple project has a rule that says:

A user may not have more than 3 private projects.”

This in TextUML looks like this:


class User 

    attribute projects : Project[*] 
        invariant Maximum 3 private projects { 
            return self.privateProjects.size() <= 3
        };
        
    derived attribute privateProjects : Project[*] := () : Project[*] {
        return self.projects.select((p : Project) : Boolean {
            return not p.shared
        });
    };

end;

(Note the constraint relies on a derived property for more easily expressing the concept of private projects, and that backslashes are used to escape characters that otherwise would not be allowed in identifiers, such as whitespaces.)

What do you think? Does it make sense? I know the syntax for higher order functions could benefit from some sugar, but that can be easily fixed later. I am much more interested in feedback on the idea of modeling with executable constraints than in syntax.

Wading in unknown waters

I am in the process of modeling a real world application in AlphaSimple and for most cases, the level of support for constraints that we are building seems to be sufficient and straightforward to apply.

I have though found one kind of constraint that is hard to model (remember, AlphaSimple is a tool for modeling business domains, not a programming language): in general terms, you cannot modify or delete an object if the object (or a related object) is in some state. For example:

"One cannot delete a project's files if the project is currently shared".

Can you think of a feature in UML that could be used to address a rule like that? I can't think of anything obvious (ChangeEvent looks relevant at a first glance, but there is no support for events in TextUML yet).

Any ideas are really appreciated.

EmailFacebookLinkedInGoogle+Twitter

Help wanted: Converting closure-based iterators into plain loops

Smalltalk, Ruby, Groovy and other languages allow one to implement loops using closures. But so does TextUML/UML. Given the primary use case of TextUML/UML is to generate code, one thorny question is how to generate code from a UML model using closures for implementing loops through collections into a language, like Java or C, just as one would normally write loops over collections in those closure-free languages.

Here are some examples of how to translate from closure-based loops (in TextUML, but the specific syntax shouldn’t matter) to ordinary loops (in Java, but again, syntax specifics shouldn’t matter):

forEach

In TextUML


self->units.forEach((u : Unit) { 
    link ProjectUnits(project := clone, units := u.clone()) }
);


In Java


for (Unit u : this.getUnits()) { 
    clone.addUnits(u.clone());
}

select

In TextUML


return Project extent.select((p : Project) : Boolean { return p.shared });

In Java


Set<Project> result = new HashSet<Project>();
for (Project p : Project.allInstances()) { 
    if (p.isShared()) {
        result.add(p);
    }
} 
return result;

collect

In TextUML


return Project extent.collect((p : Project) : User { return p->owner });

In Java


Set<User> result = new HashSet<User>();
for (Project p : Project.allInstances()) { 
    User owner = p.getOwner();
    result.add(owner);
} 
return result;

count

In TextUML


return Project extent.count((p : Project) : Boolean { return p.shared });

In Java


int count = 0;
for (Project p : Project.allInstances()) { 
    if (p.isShared()) {
        count++;
    }
} 
return count;

In AlphaSimple, we got much of what is needed above in place. There are though some additional challenges posed by the need of chaining those collection primitives, and the need for mapping the data flow that chains them together to an unchained form, using local variables in the target language. These last two aspects have been keeping me awake at night. If you feel like throwing a light (with strategies, references) on how to address that, by all means go for it, it is pretty dark in here right now… :)

EmailFacebookLinkedInGoogle+Twitter

TextUML Toolkit 1.7 RC1 now available!

The first release candidate build for TextUML Toolkit 1.7 is now available! If you already have the Toolkit already installed, please update now. If you don’t, the easiest way to install it is via the marketplace client (built into Eclipse 3.6). Or else, point the Eclipse update manager to http://abstratt.com/update.

Here is a summary of the new features:

  • the editor will auto-format your source files as you save them (you must turn this feature on via the TextUML preference page)
  • fine control over the outline contents (you can toggle attributes, operations and inner associations on and off via the TextUML preference page) – thanks to Attila Bak for contributing this feature.
  • notation features: you can apply stereotypes to parameters, and you can declare attributes as read-only – also, attributes are now public by default.

More details here.

If you find any issues, please let us know (here or on the forum) so we can fix them before declaring a release.

EmailFacebookLinkedInGoogle+Twitter

Model-driven Development with Executable UML models

Last November I did a lecture on Model-driven Development with Executable UML models to a class of Software Engineering undergrad students at UVic. Here are the slides:

I think it gives a good summary of my views on model driven development (with Executable UML or not):

  • even though problem domains are typically not very complex, enterprise software is complex due to the abundance of secondary crosscutting concerns (persistence, concurrency, security, transactions etc)
  • there are two dominant dimensions in enterprise software: business domain concerns and technological concerns
  • they are completely different in nature (change rate, abstraction level) and require different approaches (tools, skills, reuse)
  • MDD is a strategy that handles well that divide: models address business domain concerns, PIM->PSM transformation addresses technological concerns
  • brainstorming, communication, documentation and understanding (rev. engineering) are not primary goals of MDD – to produce running code in a productive and rational way is
  • models in MDD must be well-formed, precise, complete, executable, technology independent
  • graphical representations are not suitable for executable modeling (textual notations are much better)
  • diagrams != models, text != code (that would look good on a t-shirt!)

I guess those who know me won’t have seen anything new above (these ideas make the very foundations of the TextUML Toolkit and AlphaSimple).

Do you agree with those positions?

EmailFacebookLinkedInGoogle+Twitter

TextUML Toolkit 1.6 declared!

The TextUML Toolkit version 1.6 has been released. It is the same RC1 build mentioned here a week ago. The listing on the Eclipse Marketplace has been updated, so in addition to the regular update site (http://abstratt.com/update/), if you are using Eclipse 3.6, you can get it even more conveniently using the brand new Eclipse Marketplace Client.

Take a look at the new notation features:

  • preconditions on operations
operation withdraw(amount : Real);
precondition { amount > 0 and amount < self.balance }
begin
    self.balance := self.balance - amount;
end;
  • derived properties
reference employees : Employee[*]

/* calculated field */
derived attribute employeeCount : Integer := ():Integer { return self->employees.size() };
  • initial values on properties
attribute available : Boolean := true;

You can also try these new features online on AlphaSimple. Sign up or start a guest session to create, validate and run your models on the spot, there is nothing to install!

EmailFacebookLinkedInGoogle+Twitter

TextUML Toolkit 1.6 RC1 is now available

TextUML Toolkit 1.6 RC1 is now available! You can install it using the Marketplace Client or by pointing Eclipse to the update site:

http://abstratt.com/update

If you find any problems installing this build, please let us know asap so it can be addressed before the final release.

New features

Much of the work in this release went into improving the model building infrastructure to be even more notation agnostic. That work is still ongoing and should be completed in 1.7. But there were plenty of user-facing feature additions as well:

  • preconditions on operations (2986923 and 3002571)
  • support for a default notation (so file extensions can be optional) (2995372)
  • support for implicitly applying profiles/stereotypes (so models are less verbose) (2981580)
  • support for derived properties (2928428)
  • support for initial values in properties (2115439)
  • advanced features (closures, constraints) are now implemented using profiles instead of metamodel extensions (2933692)

In other news

The reason it took so long for a new TextUML Toolkit release to come about was that I have been busy working on AlphaSimple, which went on public beta today. AlphaSimple is an online tool for domain-driven prototyping that currently uses TextUML as modeling notation. Thus, AlphaSimple is also the driving force behind most of the changes that happened in the 1.6 cycle, and you can try them right away by starting a guest session and studying the example projects.

EmailFacebookLinkedInGoogle+Twitter

Interview at Modeling-Languages.com

Last December I had the pleasure of being interviewed by Jordi Cabot, the maintainer of Modeling-Languages.com, a web site on all things model-driven. We talked mostly about the TextUML Toolkit project, but Jordi also asked about my opinions on more general subjects, such as modeling notations, textual modeling frameworks, DSLs, UML and trends in modeling.

Jordi has recently made a transcription of the interview available on his web site. Take a read, feel free to leave a comment, I am very keen on discussing on any of the topics covered.

EmailFacebookLinkedInGoogle+Twitter

TextUML Toolkit 1.5 is out!

Release 1.5 of the TextUML Toolkit is now available from the update sites, for both Eclipse 3.5+ and 3.4. Update or install. We got a few new features in this release.

Content assist
There is now early support for content assist (contributed by Attila Bak), with initial support for stereotype applications.
Content assist in action

Element aliasing
You can now enable aliasing by creating repository properties in the form:

mdd.aliases.<source-qualified-name>=<target-qualified-name>

For instance:

mdd.aliases.base::Real=mypackage::MyReal

New textual notation features
There is now textual notation support for decimal literals.

You might have noticed 1.4 was announced here less than a month ago. We decided to start pushing releases as often as we can, so you can expect a bunch of 1.x releases throughout 2010. If you want to see some feature in the TextUML Toolkit, ask away!

EmailFacebookLinkedInGoogle+Twitter

Can TextUML be implemented the generative way (with Xtext or EMFText)?

I have been trying to figure out whether the TextUML notation for action semantics can be dealt with properly by tools such as Xtext and EMFText (class models and state machines should be fine). For example, given this structural model fragment:

class Advertisement 
    attribute summary : String;
    attribute description : String; 
    attribute keywords : Keyword[*]; 
    attribute category : Category;  
    operation addKeyword(keywordName : String);
    static operation findByCategoryName(catName : String) : Advertisement[*];
end;

association AdvertisementKeyword
    role Advertisement.keywords;
    role advertisement : Advertisement;
end;

class Keyword specializes Object
    attribute name : String;
end;

class Category specializes Object
    attribute name : String;
    attribute description : String;
end;

association AdvertisementCategory
    role Advertisement.category;
    role ad : Advertisement[*];
end;

Notice the Advertisement class declares two operations. Their behaviour in TextUML could be written as:

    operation Advertisement.addKeyword;
    begin
        var newKeyword : Keyword; 
        newKeyword := new Keyword;
        newKeyword.name := keywordName;
        link AdvertisementKeyword(keywords := newKeyword, advertisement := self);
    end;

    operation Advertisement.findByCategoryName;
    begin  
        return Advertisement extent.select(
            (a : Advertisement) : Boolean {
                return a->AdvertisementCategory->category.name = catName; 
            }
        );
    end;

Note that TextUML allows the behavior to be specified inline when declaring an operation in a class, or in separate, as above (that explains the lack of parameters, modifiers etc).

In the resulting UML model, the behaviour of Advertisement.addKeyword would roughly map to this (using a textual pseudo-notation for UML activities that is hopefully more readable than raw XMI):

activity(name: "addKeyword") {
    structuredActivityNode {
        variable(name: "newKeyword", type: #String)
        writeVariable(variable: #newKeyword, value: createObject(class: #Keyword))
        writeAttribute(
            attribute: #Keyword.name, 
            target: readVariable(variable: #newKeyword), 
            value: readVariable(variable: #keywordName)
        )
        createLink(
            association: #AdvertisementKeyword, 
            end1: #AdvertisementKeyword.keyword, 
            end1Value: readVariable(variable: #newKeyword), 
            end2: #AdvertisementKeyword.advertisement, 
            end2Value: readSelf()
        )
    }
}

and the behaviour Advertisement.findByCategoryName would map to this:

activity(name: "findByCategoryName") {
    structuredActivityNode {
        // implicit variable for return value
        variable(name: "@result", type: #Advertisement, upperBound: *)
        // implicit variable for parameter value
        variable(name: "catName", type: #String)
        writeVariable(
            variable: #@result, 
            value: callOperation(
                operation: #Advertisement.select, 
                target: readExtent(class: #Advertisement),
                filter: metaValue(#@findByCategoryName_closure1)
            )
        )
    }
}

// a closure is an activity that has a reference to a context activity
closure(name: "@findByCategoryName_closure1") {
        // implicit variable for return value
        variable(name: "@result", type: #Boolean)
        // implicit variable for parameter value
        variable(name: "a", type: #Advertisement)
        writeVariable(
            variable: #@result, 
            value: callOperation(
                operation: #Object.equals,
                // variables from the context activity are available here
                target: readVariable(variable: #catName)
                args: readAttribute(attribute: #Category.name, target: readLink(
                    association: #AdvertisementCategory,
                    fedEnd: #Advertisement.ad,
                    fedEndValue: readVariable(variable: #a),
                    readEnd: #Advertisement.category
                ))
            )          
        )
}

Note that UML does not have closures, this is an extension to the UML metamodel which I wrote about here before.

Some background on the metaclasses involved: ReadVariableAction, CreateObjectAction, CreateLinkAction, ReadExtentAction etc are all action metaclasses. Actions are the building blocks for modeling activity behaviour in UML.

The million dollar question is: can Xtext and EMFText handle more complex textual notations like this? Is this out of the happy path? Has anyone done something similar? I am under the impression I could use Xtext or EMFText better if I used them based on a intermediate metamodel for behavior that would be closer to the concrete syntax (to get all the IDE bells and whistles for free) and then transformed that to UML in a separate step.

If you have the answers for any of these questions (or even if you have comments and questions of your own), please chime in.

EmailFacebookLinkedInGoogle+Twitter

Eclipse Modeling Day in Toronto

Last Wednesday I attended the Eclipse Modeling Day in Toronto. Coming all the way from Victoria, I must have been the participant that came from farthest. Except, of course, those folks from SAP AG and Itemis that were presenting. But I was really glad to be there. I finally had the chance to chat and discuss about Eclipse and Modeling face to face with people like Simon Kaegi (server-side OSGi), Kenn Hussey (MDT UML2), Eike Stepper (CDO), Ed Merks and (fellow Brazilian) Marcelo Paternostro (EMF), Lynn Gayowski and Ian Skerret (Eclipse Foundation). It’s weird: I have been part of the Eclipse Modeling community since 2006 (and the larger Eclipse community since 2002), but had never actually met any of those folks before. Also amusing was that even though I worked for IBM Ottawa for quite a few years, my first time at the Toronto Lab was many years later as an ex-IBMer living on the West Coast.

The presentations I attended were in general very good. I had seen Ed Merks’ opening presentation on the web before, but this time I could ask questions. I got a better understanding of Xtext (great presentation!), CDO, and the Query, Validation and Transaction frameworks, some technologies I am considering adopting in the near future. I had a first time contact with the Papyrus 2.0 vision, which might provide an opportunity for increasing adoption of the TextUML notation by integrating the TextUML Toolkit into a larger modeling environment. And I liked the idea of a discussion panel at the end.

I had to catch a flight back home that same night, so I could not attend the Eclipse RT Day on the following day nor could I see any of the demos at the DemoCamp. Speaking of which, I did manage to stay long enough to have a beer on the Foundation (thanks!), and chat with Christopher Nagy from Dexterra/Antenna Software, who made a quick ad hoc demo to Ian and myself of their Eclipse/GEF-based tool for authoring multiplatform mobile applications. Very impressive!

As constructive criticism, I do agree with Bjorn that the format could use some tweaking. I sure had to miss some very promising presentations. Maybe with two presentation formats, a longer (for instance, 40-50 mins) and a shorter one (20-25 mins), we wouldn’t need two tracks. I am not sure presenters who came from Europe would have come had they had a shorter slot though. That being said, I was very satisfied with the event overall and am sincerely thankful to IBM Toronto for hosting the event, the Eclipse Foundation for organizing it, all the folks presenting and the companies sponsoring them. Thank you very much! As Lawrence wrote, hope we will see more events like this in the future!

EmailFacebookLinkedInGoogle+Twitter

TextUML Toolkit 1.4 is out!

Release 1.4 of the TextUML Toolkit is now available from the update sites, for both Eclipse 3.5+ and 3.4 (by the way, it is possible this will be the last major release targetting Eclipse 3.4, unless someone volunteers to generate and test builds for 3.4).

This is mostly a bug fix release. However, there were some minor notation changes to support stereotypes on dependencies, generalizations and interface realizations, hence the version change from 1.3.x to 1.4.x.

Thanks to Vladimir Sosnin, Attila Bak and fredlaviale for contributing with code and/or bug reports/feature requests. Keep them coming!

As usual, you can find a summary of the changes in the TextUML Toolkit Features page. If you are using the Toolkit, make sure you upgrade soon. If not, what are you waiting for? Install it already!

EmailFacebookLinkedInGoogle+Twitter

TextUML Toolkit 1.3 is out!

The TextUML Toolkit 1.3 is now available, 4.5 months after 1.2. If you already got RC1 (1.3.0.20090614…), it is the same build, no need to upgrade again. Otherwise, just point the Eclipse update mechanism to:

http://abstratt.com/update/ – if you are using Eclipse Galileo, or

http://abstratt.com/update/3.4/ – if you are using Eclipse Ganymede.

Please see the feature page for a summary of the evolution of the TextUML Toolkit in terms of features across releases. Some highlights for this release are better integration with other UML tools and support for both Eclipse 3.4 and 3.5.

As usual, bug reports and feature requests are much appreciated. And if you need help, make sure to ask on the user forum.

The TextUML Toolkit Team

EmailFacebookLinkedInGoogle+Twitter

TextUML Toolkit 1.3RC1 is now available

The first release candidate towards version 1.3 of the TextUML Toolkit is now available. The feature overview page has been updated to reflect the changes in the 1.3 cycle.

Please give it a try and report any issues you might find. If no serious problems are found with this build, it will be promoted to 1.3 final later this week.

Note that the main update site supports Eclipse 3.5, but there is also an update site for Eclipse 3.4.  The features are the same, the only differences between the two sites are the dependencies (UML2 3.0 and EMF 2.5 on Eclipse 3.5, UML2 2.2 and EMF 2.4 on Eclipse 3.4).

The TextUML Toolkit team welcomes your feedback.

EmailFacebookLinkedInGoogle+Twitter