Language

The language used in Cloudfier is UML, via TextUML, a textual notation for UML. This page is not a replacement for the TextUML documentation (primarily the docs on structural and behavioral modeling), but it instead demonstrates how to use that notation when creating Cloudfier apps. Also, make sure you are familiar with the concepts used in Cloudfier apps.

Before you start, make sure you sign into Cloudfier, so you can put what you learned to practice right away!

mdd.properties

The mdd.properties file is a project configuration file. Any Cloudfier project must have this file, alongside with the actual application source files. The file is created automatically for you if you use the cloudfier init-project command, but you may also add it by hand. It should look like this:

mdd.modelWeaver=kirraWeaver
mdd.enableExtensions=true
mdd.enableLibraries=true
mdd.enableTypes=true
mdd.extendBaseObject=true

# enables logging it as guest
mdd.application.allowAnonymous=true

# application title and name
mdd.application.title=My New To-Do Application
mdd.application.name=todo-app

Entity and properties

This simple app shows an entity with a couple of properties, one required (name) and another optional (birthDate):

package customers;

role class Customer
  attribute name : String;
  attribute birthDate : Date[0,1];
end;

end.

As you can tell, Cloudfier maps TextUML/UML classes to entities. Note also that Cloudfier currently does not support multivalued properties.

Role entities

Any application needs to keep information about their users: for example, employees in an expense reporting application, or customers in an e-commerce application. The entities (classes) that represent roles of users in an application must be marked with the role modifier.

Relationships

This app is a bit more elaborate and shows three entities connected via two relationships, one, mandatory and single (Expense’s category), and another, multiple and optional (Employee’s expenses).

package expenses;

class Category
 attribute name : String;
end;

class Expense
  attribute amount : Double;
  attribute date : Date;
  reference category : Category;
end;

role class Employee
  attribute name : String;
  reference expenses : Expense[*];
end;

end.

Relationships can be defined in other ways. See the TextUML documentation on associations for more information.

Actions

The app below shows actions defined on an entity:

package banking;

class Account
  attribute number : String;
  attribute balance : Double;
  operation deposit(amount : Double);
  begin
    self.balance := self.balance + amount;
  end
  operation withdraw(amount : Double);
  begin
    self.balance := self.balance - amount;
  end;
end;

end.

Queries

Queries are modeled as static query operations (use the query keyword instead of operation) that return a set of entity instances. The example below shows a query:

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

State Machines

The app below shows a state machine defined on an entity:

package petstore;

class Order
    readonly attribute orderDate : Date := {Date#today()};
    attribute customerName : String;
    attribute orderStatus : Status;
   
    operation complete();
    operation process();
   
    statemachine Status
        initial state New
            transition on call(process) to Processing;
        end;    
        state Processing
            transition on call(complete) to Completed;
        end;
        terminate state Completed end;
    end;
end;
end.

Data types

The following built-in data types are avaiable:

  • Integer
  • Double
  • Date
  • String
  • Memo
  • Boolean

You can also use enumerations and other entity types.

Services

Services are provided/required via ports which are explicitly connected via connectors.

Declaring a service

A service declaration involves declaring:

  • a service interface
  • a service implementation
interface Multiplier
    operation multiply(v1 : Double, v2 : Double) : Integer;
end;

class MultiplicationService implements Multiplier
    operation multiply(v1 : Double, v2 : Double) : Double;
    begin
        return v1 * v2;
    end;
end;

Declaring a service client

In order to require a service, you need to declare a requiring port typed by the required interface.

class OrderItem
    required port multiplier : Multiplier;
    attribute quantity : Integer;
    attribute unitPrice : Double;
    derived attribute totalPrice : Double := {
        self.multiplier.multiply(quantity, unitPrice)
    };
end;

Hooking up the client and provider

Finally, to hook clients to a service implementation, you need to offer an instance of that service through a port provided by a component and connect it to the requiring port:

component OrderApp
    composition orderItems : OrderItem[*];
    composition multiplicationService : MultiplicationService;
    provided port multiplier : Multiplier
        connector orderItems.multiplier, multiplicationService;
end;

In this example, the OrderApp component contributes an instance of MultiplicationService as an implementation of the Multipler interface. Then, it declares a providing port called ‘multiplier’ and connects it to requiring port also named ‘multiplier’ in OrderItem.

Note that an application must have all required ports connected to some compatible providing port, or a compilation error will occur.

Signals

Signals are especial data types describing events.

Declaring a signal

signal ExpenseApproved
    attribute employeeName : String;
    attribute amount : Double;
    attribute description : String;
    attribute expenseId : Integer;
end;

Sending a signal

You usually send a signal to some service object accessed via a required port.

    private operation reportApproved();
    begin
        send ExpenseApproved(
            employeeName := self.employee.name,
            amount := self.amount,
            description := self.description + "(" + self.category.name + ")",
            expenseId := self.expenseId) to self.expensePayer;
    end;

Receiving a signal

The example below shows a class that receives several kinds of signals and reacts by sending another signal.

class EmailUserNotifierService implements shipit::UserNotifier

    reception(n : IssueReported);
    begin
        send EMailMessage(
            subject := "New issue: " + n.issueKey + " - " + n.summary,
            body := n.description,
            \to := n.userEmail,
            \from := "shipitplus@cloudfier.com") to self.emailer;
    end;

    reception(n : CommentAdded);
    begin
        send EMailMessage(
            subject := "New comment to: " + n.issueKey + " by " + n.author,
            body := Memo#fromString(n.author + " said: '" + n.comment + "'"),
            \to := n.userEmail,
            \from := "shipitplus@cloudfier.com") to self.emailer;
    end;

    reception(n : IssueResolved);
    begin
        send EMailMessage(
            subject := "Issue resolved: " + n.issueKey,
            body := Memo#fromString(n.issueKey + " has been resolved as: " + n.resolution),
            \to := n.userEmail,
            \from := "shipitplus@cloudfier.com") to self.emailer;
    end;

    required port emailer : Emailer;
end;

What next?

Hope you had fun trying these examples in Cloudfier. Now extend them with your own ideas! Or for more examples, try the Example Apps.