Object language of restrictions (and a little about metamodels)

  • Tutorial
image

In our opinion, the Object Constraint Language (OCL) should be known to everyone who is involved in modeling or who are interested in model-oriented development. However, he is undeservedly deprived of attention on the network in general, and, indeed, in the Russian-language segment of information is just a miserable one. What kind of language it is and why it is needed is described in this article. The article does not pretend to be fundamental, complete in scope, accuracy of definitions, etc. Its task: 1) to introduce to simple examples with OCL those who have never heard of this language, 2) and for those who have heard of it, it is possible to discover new ways to use it.

Structural and additional restrictions


Let's start right away with an example. Suppose we are developing a program such as Jira to account for projects, tasks, to distribute these tasks among employees. The data model for such a program might look very simplified like this.

image

Having drawn such a diagram, we imposed restrictions on our subject area: we fixed that only employees, tasks, and projects can exist in it, that they possess exactly such attributes, that they can be connected precisely by such connections. For example, in our subject area, an employee must have a full name and date of birth. But a task or project cannot have such attributes. Or the executor of a task can only be an employee, but another task or project cannot be an executor. These are obvious things, but it’s important to understand that when creating such a model, we formulate the limitations. Such constraints are sometimes called structural constraints.

However, often structural limitations when modeling a domain are not enough. For example, we may need a restriction that the project manager cannot be a participant in the project at the same time. Now this limitation does not follow from this diagram. We give other examples (with a catch ;-)) of additional (non-structural) restrictions:
  1. Trainee cannot manage projects
  2. A programmer can manage one project, but cannot participate in other projects.
  3. A lead programmer can manage no more than two projects at a time
  4. The project should have only one leader
  5. Full namesake can't participate in one project
  6. A closed project cannot have open tasks (a task is considered closed if it has an actual execution time)
  7. Before assigning an executor for the task, the planned execution time must be determined
  8. Before closing the task, the planned execution time must be determined
  9. An employee in one project can have only one open task and no more than 5 tasks for all projects
  10. Employee must be of legal age
  11. Name of employee should consist of three parts (last name, first name and patronymic), separated by spaces (spaces cannot be double, triple, etc.)
  12. Lead Programmer Can't Call Sigmund
  13. The actual time of work on the task cannot exceed the planned more than two times
  14. A task cannot be its subtask
  15. The planned time to complete the task should be no less than the time planned for the subtasks
  16. ... come up with a few limitations yourself ...

Summary

Various modeling languages ​​allow you to describe the structural constraints imposed on the subject area:
  • on types of objects: an object can only be an instance of the corresponding class (an employee cannot possess project properties, a task cannot be saved to a table with employees, etc.);
  • acceptable properties and associations;
  • on types: properties can take values ​​only of a certain type;
  • multiplicity: values ​​of required properties / associations should be indicated, for properties / associations with a plurality of “1” several values ​​cannot be indicated, etc.

Additional restrictions can be described using additional languages ​​such as OCL.

Configure Eclipse


If you want to try all this in practice, then Eclipse is required. If not, continue to the next section.
  1. Download and unzip Eclipse , preferably Eclipse Modeling Tools. If you are already using Rational Software Architect 9.0 (which is based on Eclipse), you can use it.
  2. Install the necessary libraries:
    • For Eclipse. Select Help -> Install Modeling Components from the menu. In the window that appears, select: “Graphical Modeling Framework Tooling”, “OCL Tools” and “Ecore Tools” will not interfere.
    • For RSA 9.0. Copy the org.eclipse.gmf.tooling.runtime _ *. Jar and org.eclipse.ocl.examples.interpreter _ *. Jar files to the C: \ Program Files \ IBM \ SDP \ plugins \ folder (depending on the installation, the folder maybe a little different).
  3. Install pm * .jar plugins with test metamodel:
    • For Eclipse. Copy these files to the "$ ECLIPSE_HOME / dropins" folder
    • For RSA 9.0. Copy these files to the folder “C: \ Program Files \ IBM \ SDP \ plugins \” (depending on the installation, the folder may be slightly different). The dropins folder is not recommended because of some bugs.
  4. Restart Eclipse. Select File -> New -> Other ... from the menu, write “pm” in the search bar. “Pm Model” and “Pm Diagram” should appear.
  5. You can either create a test model yourself or take a finished one
  6. Open the console: Window -> Show View -> Other ... Find "Console" in the list.
  7. In the console window, open the "Open Console" drop-down list and select "Interactive OCL" in it (see the figure below).
  8. Select any object on the diagram and write “self” at the bottom of the console, press “Enter”. If everything is set up correctly, then you will see something like this:



If the plugin with the metamodel does not work for you, you can compile it yourself from the source codes . It uses a relatively old version of GMF to make it work in RSA 9.0.

Example No. 1. The planned time for executing a task should be no less than the time planned for subtasks


The first thing you should know about OCL is that a constraint always applies to a particular class of objects. Before writing an OCL rule, we need to select this class. Sometimes this choice is not very obvious, but now everything is simple: the rule applies to the task. We can access a specific instance of a task using the self variable. If it is necessary to get the value of some property of the problem, then after the variable name we write a point and then the name of this property. Similarly, you can specify the name of the association. Try to do it in Eclipse.

Note

To avoid unnecessary problems, in the model all classes, properties, etc. named using latin characters. You can find out exactly what this or that property is called using auto-completion (called by pressing Ctrl + SPACE).

The final control rule is shown in the figure.



The not-so-trivial thing for newcomers to OCL is the difference between the "." and "->".
What comes after the point refers to each element in the collection of values ​​or objects.
What comes after the arrow refers to the entire collection of values ​​or objects.
The table shows some examples of using points and arrows.
OCL expressionInterpreting an OCL Expression
self.plan_timeGet the value of the "Plan_time" property
self. SubtasksGet many subtasks
self. Subtasks. time_planFor each subtask in the set, get the value of the "Plan_time" property and finally get a lot of such values
self. Subtasks. Time_plan -> sum ()Calculate the sum over the whole set of values
self.Plane_time-> sum ()Despite the fact that a task can have at most one value for the “Plan_time” property, this value is implicitly converted to a set (with a single element). The sum () operation is applied to the resulting set.
Note

Refer to the OCL specification for a description of the collect () and oclAsSet () operations.

Example No. 2. The project manager should not be a participant in the project.


The figure shows several equivalent formulations of this rule: we get a list of project participants and make sure that there is no leader in it.



The self variable can be omitted; it is implied implicitly. However, it is important to note that some operations (select, exists, forAll, ...) create a new scope with additional implicit variables (iterators). At the same time, it is rather difficult to understand to which implicit variable the property chain belongs. In such situations, it is highly advisable to specify all variables (or all but one) explicitly.

Example No. 3. A lead programmer can manage no more than two projects at a time


Beginners when writing such rules usually first of all ask if in OCL if-then-else. Yes, there is, but in most rules it is better to use implication instead.



If the premise of the implication is false, then we don’t even try to check the rest of the conditions. And if true, then we get a list of projects that the employee manages, exclude closed projects from them, and if the number of such projects is no more than two, we believe that the condition is met.

Example No. 4. Full namesakes cannot participate in one project


Here is perhaps the first non-trivial rule. You can try to formulate it starting with both the employee and the project. It is important to start with the correct class :-) If you do not know about the existence of the isUnique () operation, then you can start framing monsteroidal constructions, just like I did when I first encountered such a task.



Remove the explicit iterator declaration from the rule (the “y” variable), and try to interpret the rule. Personally, I don’t remember exactly what will happen. It may be a mistake that it is impossible to unambiguously determine which name is being addressed. Or maybe the iterator takes precedence over self. Need to look at the specification. In any case, it is advisable to avoid such situations, and specify variables explicitly.

Note

Iterators can be more than one. Take a look at the OCL specification , experiment in the OCL console. See how the specification of the operation isUnique () is defined.

Example No. 5. An employee in one project can have only one open task and no more than 5 tasks for all projects


In this rule, we first declare a variable with a list of open tasks. Then we check for them two conditions. We assume that a task is considered open if the actual execution time is not indicated for it.



What class do you think is best to define this rule? For an employee or task?

Example No. 6. A task cannot be its subtask


If you did not immediately understand the essence of the rules depicted in the figure, then this is quite natural. On the one hand, there is a class of objects "Task", on the other - the task has an association "Task", which indicates the parent task. It is advisable to name classes and properties / associations in different ways, this improves the quality of the model and simplifies the understanding of OCL rules.



Let's start by implementing the “forehead” rule. Let’s check if the association “Problem” of a certain task indicates the task itself: “self. Task <> self”. In the figure, we removed the explicit reference to the self variable. It would seem that this is not fatal, but in OCL expressions we can refer not only to properties or associations, but also to classes. For example, using this expression, we can get a list of all tasks: "Task.allInstances ()". In this example, the OCL interpreter will most likely decide that it is a “Task” association, not a class. However, I repeat, it is advisable to avoid such ambiguities.

The second drawback of this rule is that it does not take into account that subtasks can have their own subtasks. A task can easily turn out to be a subtask of its subtask. To make sure that this is not so, we might need loops or recursive functions. But there are no loops in OCL, and with functions everything is not very simple. But there is a wonderful closure () operation. This operation is defined for collections, so you need to put an arrow in front of it, not a dot. For each element of the collection, the closure () operation computes the expression specified as its argument, then combines the obtained values ​​into a collection, for each element of which evaluates this expression again, adds the obtained values ​​to the resulting collection, and so on:

self.Задача->union(self.Задача.Задача)->union(self.Задача.Задача.Задача)->union(...)

The figure shows two versions of the rule based on recursion for parent tasks and subtasks, respectively. What do you think is the best option (trick question)?

Note

Surely, sooner or later you will need cycles. In most cases, you can use the operations defined for collections (select, exists, ...) instead.

If you still need a loop, then you can try something like this:

Sequence{1..10}->collect(...)

Also note the iterate () operation.

Example No. 7. The name of the employee should consist of three parts (last name, first name and patronymic), separated by spaces (spaces cannot be double, triple, etc.)


Obviously, such a rule would be easiest to implement using regular expressions. However, there are no operations in the OCL specification to work with them. In the figure you see examples of the implementation of such a rule without the use of regular expressions. The good news is that the standard OCL library is extensible, and in some implementations the matches () operation still exists, or it is possible to implement it yourself.



OCL constructs


All the OCL expressions that we have reviewed are written in Basic OCL (Essential OCL). (By the way, how do you think they differ?) Let's list the main constructions that we used:
  • Self variable - an expression is always bound to a specific class of objects
  • Accessing properties (and associations) by name:
    • self.Actor.Tasks.Project.Manager.Tasks ...
  • Arithmetic and logical operations
  • Function Call:
    • Name.substring (1, 5)
    • Name.size () = string length
  • Work with collections:
    • Name -> size () = 1, because the employee must have a single name
    • Tasks-> forAll (task | task. Time_plan> 10)
  • Variable Declaration:
    • let s1: Integer = name.indexOf ('') in
  • Conditional statement (if-then-else)

Complete OCL has some additional constructions that we will not consider in detail; you can familiarize yourself with them by reading the specification:
  • Messages, states
  • Packages, context, expression purpose (package, context, inv, pre, post, body, init, derive)

Homework


See the OCL specification .

Understand primitive data types, types of collections (multiple, sequence, etc.). Why do you think there are no primitive types for working with date and time in OCL? What if you still need them?

Understand the differences between Basic OCL, Essential OCL, Complete OCL.

Implement other OCL rules yourself, check them in Eclipse.

All OCL-expressions that are given in the article can take only true or false value. (By the way, do you agree with this statement?) In your opinion, can OCL expressions have a numerical range of values, return strings, sets of values ​​or objects? How could such non-Boolean OCL expressions be used?

In your opinion, can the result of evaluating an OCL expression be another OCL expression?

Bonus A bit about metamodels


All that we have done before is to describe the limitations of our model and check these restrictions on specific employees, tasks, and projects. For example, we described a rule that a project manager cannot be a participant in a project at the same time. Moreover, we described this rule for all projects and employees in general. And then they checked it for specific projects and employees.

Example No. 1. Metamodel “Employee-Task-Group”

Now let's go up one level. For example, we want to develop another application - for a taxi order service. There will be a similar data model. Instead of employees, let there be drivers, instead of tasks - orders, and instead of projects - cities. Of course, this is a very conditional example, but it is only needed to demonstrate the idea. Or, say, we need to develop an application for making an appointment with a doctor. There will be doctors, departments and receptions.

We can look at all these models, and see the general patterns in them. The programmer, driver and doctor are essentially the same thing - just an employee. Order, reception - this, if summarized, is simply a task. Well, let's summarize the city, branch, project. We simply call them a group. The connection between the employee and the task is called execution. The connection between the employee and the group is called participation.



Having drawn such a picture, you and I have risen to a new level of awareness of the subject area - we have developed a metamodel, a language in which we can describe any such model.

Let me remind you that the model that we built in the first part of the article imposed some structural and non-structural restrictions on information about objects in our subject area.

Similarly, a metamodel imposes structural and non-structural constraints on models that are built in accordance with this metamodel. For example, in any model that corresponds to this metamodel, there can only be elements that are based on metaclasses marked in red in the figure. For example, the class “Building”, “Road”, or something similar that is not an employee, task, etc., cannot appear in the model.

Примечание

К слову, метамодели строятся на основе метаметамоделей (например, MOF, Ecore). Если вам это интересно, почитайте спецификацию OMG MOF. В общем случае, может быть произвольное количество уровней моделирования, однако обычно достаточно 2-3.

Кстати, как вы считаете на основе какой метаметаметамодели построена сама MOF?

Если вы не боитесь вывихнуть мозг, то почитайте статью Dragan Djuric, Dragan Gaševic и Vladan Devedžic «The Tao of Modeling Spaces»(I personally have a dislocation of the brain from their last names). By and large, when developing software, everything is a model (from a mental image in the mind of the developer to source code, documentation, test scripts, etc.), and the development process is the conversion of some models to others. We will try to disclose the topic of model transformation in subsequent articles.

Do you think the model shown in the figure above (green rectangles, blue lines) corresponds to metamodels (red rectangles)?

Obviously, in addition to structural constraints, the metamodel may contain non-structural constraints. We can describe the latter again in OCL. Examples of such rules are shown in the figure.



Примечание

На самом деле этот рисунок не очень корректный. Вместо полноценной метамодели тут используется UML-профиль, который, строго говоря, является обычной UML-моделью, построенной на основе метамодели UML (которая, в свою очередь, построена на основе метаметамодели MOF). Однако, по сути, метамодели часто строятся не с нуля, а в виде UML-профиля. Например, в стандарте ISO 20022 метамодель реализована в двух равнозначных вариантах: 1) полноценная метамодель, основанная на Ecore, и 2) UML-профиль. С точки зрения целей данной статьи эти нюансы не очень существенны, поэтому будем считать UML-профиль «метамоделью».

Пример № 2. Метамодель «Сущность-Атрибут-Связь»

The metamodel described above looks a bit artificial and worthless. Let's try to build a more adequate metamodel. Suppose we need to develop a language that allows us to describe the data structure in a relational database. In this case, the difference between participants, tasks, projects, etc. not so important. All these are entities that can have attributes, and which are interconnected by relationships. In our example, there are two types of relationships: one-to-many, many-to-many. The metaclasses of such a metamodel are described by red rectangles in the figure. Do you think the model shown in the figure (green rectangles, blue lines) corresponds to metamodels? Can each element of this model belong to one of the metaclasses?



Now, finally, let's move on to practice. For this I will use Rational Software Architect, you can use any sane UML editor. It would be possible to show everything on the example of the free and ideologically correct Papyrus, but, unfortunately, it is not very convenient.

So, create a new empty project in the UML editor. Create a UML profile with the following stereotypes.



Note

Pay attention to the properties of the Attribute stereotype. This is a list of properties that any attribute in our model may possess.

Then create the following UML model, apply the profile you just created to it, apply the necessary stereotypes. If you are too lazy to create all this, you can take the finished project .



Roughly speaking, we built the model in accordance with our “metamodel”. Each element of our model is an “instance” of some metaclass from the “metamodel”.

Примечание

Ещё раз повторюсь, что, строго говоря, это не так. И профиль, и стереотипы, и модель, и все элементы модели – всё что мы с вами создали – это экземпляры метаклассов из метамодели UML. Т.е. и профиль, и модель, которая его использует, находятся на одном уровне моделирования. Но с практической точки зрения, мы можем считать этот профиль «метамоделью», в соответствии с которым создана наша модель. Если вы понимаете о чём идет речь, то до понимая того что такое метамодели вам остается ещё один шаг – понять чем являются прямоугольники и линии на диаграммах, и понять чем является xmi-файл с точки зрения моделирования. В этом вам поможет статья, которая упоминалась выше.

Now we will describe some additional limitations of our metamodel in OCL language. For example, attributes must belong to entities. If we created a metamodel in the form of a full-fledged metamodel, and not a profile, then this rule would look very simple:

owner.oclIsKindOf(Entity)

Moreover, we would not even have to describe such a rule. We would simply create an owner association between Attribute and Entity, which in principle cannot bind elements of other types.

However, our metamodel was created as a UML profile, so the rule is necessary and, at first glance, it does not look very trivial:

base_Property.class.getAppliedStereotype('PMProfile::Entity') <> null

This rule applies to the Attribute stereotype, which extends the UML metaclass “Property”. This means that in the model we can bind an instance of the Attrbiute stereotype to some property (an instance of the UML metaclass “Property”). In the latter, we can set certain values ​​minLength, maxLength and pattern. The OCL rule that we wrote above will check just this instance of the Attribute stereotype.

Through the base_Property property, we move from the stereotype instance to the property. Then, through the class association, go to the class to which the property belongs. And finally, we check whether the stereotype “Entity” is applied to this class.

Note

I do not know how much this corresponds to the specification, but sometimes the association between an instance of a stereotype and an instance of a UML metaclass (in this case, base_Property) can be omitted, it will be implicitly implied as self.

Note

If you have a question, how did I find out that you need to use the class association, that is, two ways: 1) auto-completion in Eclipse (Ctrl + SPACE) and 2) the OMG UML specification .

The rule above uses the standard trick for Eclipse-based UML editors. However, the getAppliedStereotype () operation is a non-standard extension of OCL; in other tools, it may not be supported. The same rule can be written as follows:

class.extension_Entity->notEmpty()

We check whether the class to which the property belongs has a connection (through the extension_Entity association) with the extension based on the Entity stereotype.

Note The

second option theoretically looks more consistent with the standard. However, there may be problems with it in older versions of Eclipse, it is recommended to use the first option for them.



Note

Try to find the associations base_Property, extension_Entity, and class in the profile.

The figure shows three more rules
  1. The attribute must have a type.
  2. In a one-to-many relationship, the multiplicity at one end should be no more than 1, and at the other end should be more than 1.
  3. All properties that belong to an entity must be either regular attributes or relationship roles.

Do you think it would be possible to replace these rules with the usual structural restrictions if we created the model not as a UML profile, but as a metamodel based on MOF?

MOF and Ecore are very similar to each other. Can you imagine any fundamentally different metametamodel?

Examples of metamodel constraints

Below are examples of the simplest and most complex rules from one real metamodel that we created. On the one hand, this is of course an example of terrible code, but on the other, a demonstration of some OCL constructs :-) The

maximum repeatability of the ADT component should be greater than 0:

upper > 0

The ADT component that is inherited through the constraint relationship must be in the same position as the corresponding component of the parent type:

datatype.generalization->exists(
  getAppliedStereotype('SomeProfile::restriction') <> null)
implies (
let parent : DataType = datatype.general->any(true).oclAsType(DataType) in
let props : OrderedSet(Property) = parent.ownedAttribute->
  select(getAppliedStereotype('SomeProfile::Component') <> null) in
let cur : Property = props->select(x|x.type=self.type)->any(true) in
cur <> null implies (
let prevEnd : Integer = props->indexOf(cur) - 1 in
prevEnd = 0 or (
  let allPrev : OrderedSet(Property) = props->subOrderedSet(1, prevEnd) in
  let requiredPrev : OrderedSet(Property) = allPrev->select(lower > 0) in
  requiredPrev->isEmpty() or (
    let prevStart : Integer = allPrev->indexOf(requiredPrev->last()) in
    let allowedPrev : OrderedSet(Property) = allPrev->
      subOrderedSet(prevStart, allPrev->size()) in
    let index : Integer = datatype.ownedAttribute->
      indexOf(self.oclAsType(Property)) in
    index > 1 and (
      let selfAllPrev : OrderedSet(Property) = datatype.ownedAttribute->
        subOrderedSet(1, index - 1)->
        select(getAppliedStereotype('SomeProfile::Component') <> null) in
      selfAllPrev->isEmpty() or (
        let prevType : Type = selfAllPrev->last().type in
        allowedPrev->exists(x|x.type=prevType)))))))

Why do you need OCL


The control rules in the profile or metamodel - you already know everything about this.

Control rules in the model - and that too.

The specification of operations and calculated properties - you can get to know this yourself by reading the OCL specification .

Model conversion ( QVT , MOF M2T ) - we will talk about this in the following articles.

Cognition of the meaning of being - for this, meditate on the first drawing. Where would you draw Jesus? And the Matrix?

Also popular now: