OCL Editor, system prototyper and ViewModel


We continue to introduce the MDriven Designer as a UML modeling tool that allows you to capture sufficient details to cover every aspect of a software system. From this series, you will discover how to work with the new OCL Debugger editor, system prototyper, and View Model editor. We will re-create the model of the "lifecycle" of a house from planning to demolition, also structure the concept of the "House" class, and make it possible to work with the user interface.

To make your experience smooth, we set the main tags mentioned in the video to the right bar menu of this mini-player. Choose an interesting subtitle on the list and immediately get to the exact theme navigation item place in the video. Now you can pick any topic to be instructed on without watching the whole video.

Additional diagram Nullable types State Machine Guards and triggers OCL Editor Methods on the class System prototyper New OCL Debugger • Introduction • Using “memory”, M1, M2, M3 • XML persistence • SQL persistence • MDriven server View Model Editor "ModelView" and "ViewModel" patterns

Follow the Video in Text Format

Diagrams

Continuing on the types of diagrams, we will talk about the additional one. We start working by double-clicking beside the name of the class and by choosing the actual name, we enter the edit state. This type of diagram is an automatically generated display of the class, its attributes, and its relations. Withal it serves as a navigation tool for the other existing classes - it has relations to associations. This is another way to walk around your complete model as you can observe each element in the repository and are unable to hide anything from view.

To explain the default repository graphical display, coordination works in this way: if you select the relation, you get the properties; if you choose the class, you get the properties as well. Whenever you select the class in the circle around the centre class, it will navigate to that class, and by clicking an attribute, you get its properties.

Properties should have a type, but the “?” behind it is the C# syntax to denote nullability - able to have a "null" value. In MDriven, we call this "Allow NULL". For instance, this property is "True" and it is randomized as a "String?"; if we were to change this to "False", the "?" would disappear.

States and Statemachines

The “State” attribute has the “S” icon on it. Let's go back to the "state diagram" by clicking the dialog box. Once reaching the "State diagram", on the Property Inspector dialog, we see that the "State attribute" is "State”, in the ”House.State". We are able to change its name and give the category and "ColorOnNew" states.

Depending on what dynamics you would like for your Class, you can describe them in a state machine. A state diagram for an e.g. "House" would be the "Plan" to build a "House". Evidently, "Construction" will be the next step followed by the "Maintenance" phase. Finally, in this "State Machine"  might be a "Demolition" phase. Obviously, during the "Construction" phase, there might be additional sub-states. In this case, we appear with a "Region" to the "Construction" phase. Continue with states in this Region named "GroundWork" and "Building". In this way, whenever the "House" is created, it will start with the e.g. Initial State. Since there are no “guards” stopping it from going further, it will end up in "Plan". However, we will need a "trigger" to take it forward called "StartConstruction".

Whenever we enter "Construction", charge "GroundWork” as the initial step. Whereupon, by creating a trigger "StartBulding”, we are able to go on "Building" a "House".  Creating a "ConstructionDone" trigger, we move to the next “Maintenance” step. The final “Demolish" can be triggered with the “Demolish” as well.

You can compose these state machines as simple or as complex as needed. Whether you need other acts before "Construction", or there's a domain rule that should be fulfilled, we can have these in "Guards". 

OCL

The language we will use throughout defining our model is OCL (object constraint language). In order to start “Construction”, with its help we can create "Address" for our “House”, distinct from "NULL”. OCL is beneficial because it constantly uses the language from our model and merely adds a few operators to work on the information that we have to define in the model. We can take care of our quality model and design everything according to expectations when we speak about these things. The rules become easier to read; in this case, the "Address" should not be "NULL", in order to be able to start "Construction".

Heading back to the "House" class, the triggers that we added have been added as "Methods" on the "House". This is one application mode of methods on the class, but we could just as easily add another method, that isn't a trigger, the symbol "tr" for the trigger is absent. And we could implement this by saying that it should have a “ReturnType” of type "String". Setting the parameters for the trigger:

Signature      Method1 ("param1:integer, param2:integer):String

Using the Action Editor Body, we can implement what the logic in the method should be. We refer to OCL as an "action language" because while using the methods, we are allowed side effects to be more flexible with changing data.

For instance, this method should just take:

"param1"+”param2”

It displays the “WrongExpectedType, as the "param1" and "param2" return "Int32" as an integer, although the required type as we define our method was "String". We could take the result from this and state it should be "asstring" instead.

so then we get a green light

("param1"+”param2”).asstring

Considering another case, we will instead change the “Return type” to an integer at the Properties Inspector. Checking that, we see that it’s green and this "System.Int32"  is the return type and the expected "Required return type:" "System.Int32".

What if we have typed something excessive?

"param1"+”param2x”

The response is "wrong". If we were to save this model, we see that we have an error in the:

"Method:House.Method1:7: “param2x" is not a type name

It's unrecognizable. Having fixed and saved that, we don't have any errors.

This is one way to add logic to the definition of your model. Without executing anything of this subject, just defining and assigning it. Switching back to a diagram, we can see that the trigger methods, that were created as well as "Method1" were added. This way, we demonstrate the definition, and how to navigate around designing the model.

What if we use the “Play” option to bring up a "System prototyper" dialogue?

The first step is that we should "Select persistence". "Persistence" is where data is stored.

Step1 Choosing persistence "None"

Step 2 There's no persistence configuration for this →

Step 3 is to start a system running our model, so we will press “Start System”.

Then we have a few options, implementing "New Debugger" to execute the definition that we have in the model and launching “OCLExecuteandDebug” window.

Afterward, we can't actually get to the “design surface”; however, the "Prototyper" is the model we can close it with and still have the “Debugger” available. “OCLExecuteandDebug” is operated with OCL expressions, in case, by going to "edit", we will get the same "OCL Editor" as we've seen before. For instance, we pick the class "House" and there we should find and create some "operators". There is no "create operator" in OCL, as it does not have side effects nor the ability to change data.

Another operator that's common in use in OCL is to bring up all the instances of the type "House":

House.allinstances   →   "Execute"

Here we get a result list of all the "House" class attributes, which is empty. This way we need to be able to execute some action language

"Prefix a row with ocl:, action:, oclps: (ocl:default)

  • ž   "action:"  is actually to state that the expression should be action language instead of OCL

How does the debugger work?

As long as there is an empty line between expressions, the debugger will think of it as a separated expression. So "execute" will be applied to the item that we are pointing at only.

Entering "Edit" again, we get into the “Action Editor Execute and Debug”, where "Create" is an operator:

“House.Create”

When in the Action Editor, by pressing "Ctrl+Enter", we reach a code completion type list, picking the one to quicken my typing.

In case we were to execute “House.Create” nothing much happens except, there is a new row in the "Result" set, which shows all instances. In the new "House" we see that the state is "Plan". Thus according to our "State machine", it should go to "Plan" initially.

Let's get a handle to make a "House". On the “OCLExecuteandDebug”, we see a list of "Available variables" of M1, so there's a quick way to access M1, M2, and M3. Which stands for "memory one", "memory two" and "memory three" like on a calculator.

Having marked a row and chosen "Selected to M1":

M1:     Collection(House) = (1 item)      "M1" at the “Available Variables”, is a collection of "House" and it has one item

Performing a trigger on the M1:

action:

M1   → “Edit”

M1 is a collection, so we want to reduce it to the first object. And then we will complete the operation. According to State Machine, it is "StartConstruction".

M1 → first.StartConstruction

Executing this now we shouldn't be able to get to the “Construction” phase, as the guard

“StartConstraction” [sel.Adress.notnull]  isn't fulfilled

At this point, we need to check

action:

"M1 → first.address.isnull" → "Execute"
"M1 → first.address.notnull → "Execute"

The "Address" field even if it was knowledgeable, was not "null" performing apparently as an empty string. Which is very similar to "null", but it's not "null". So maybe we should change our "null" to "Address".

There's an operator from C# - "IsNullOrEmpty", checking strings in the condition of “null” or “empty”

self.Address → IsNullOrEmpty

In order to make sure that it isn't, we add a "not" in front of the "boolean" result:

not self.Address → IsNullOrEmpty

Our rule stayed the same, instead. That doesn't help much here, because this works on the model we had when we started the "Debugger". Starting everything over with "re-read the model” will work on the current model, so if we execute the "House.allinstances" – we have no "Houses" in the “Result List”, that's because of their persistence, which we chose to use, was “none” -  it remembers nothing while starting over.

This is easily fixed by creating the "House" again

action:

House.Create   →  “Execute”

Now we have planned “House” to "StartConstruction". Initializing M1 selected as "House", we execute

M1 → first.StartConstruction

In case it says "Unable to find a transition for triggers "Start Construction" from state "Plan", while it should state "unable to find a valid transition". We need to ask the system if it is allowed to use a trigger.

M1 → first.StartConstruction?

For each of these transitions, there is also a check, that is denominated with the "?" behind it.

Here it returns an empty box, not checked, which denotes "false", so we are not allowed to walk this way in the state machine - the business rule prohibits it.

Another question about the ability to update or set the "Address".  As setting a value is a side effect, we need to be in the action language.

How would you assign a value in the action language?

Displaying the “Action Editor Execute and Debug”, there is an operator starting with a ":="

M1  →    first.Address:=

For those who acknowledged using Pascal or Delphi, it will be recognizable as the assignment operator ":=". Separating it from a simple "=", which is just equals. As this is an assignment and we want to assign it to a "string", it should not be empty, filled with an actual address.

M1   →     first.Address:="youraddress"  →  “Execute”

This way, by executing the valid expression, we are allowed to "StartConstruction". Working on the "House" and "StartConstruction" state, we immediately enter into the "Construction" outer state. As it is forced to the "Start" state for "Region1" and there is no guard on the way from the initial point of the inner region, it will end up in "Construction.GroundWork".

"StartConstruction" "Construction" outer state → "Start" state for the "Region1" → "Construction.GroundWork"

Discussion on Modeling

We have learned how to define rules and to create a model to work on and create data according to this model following these rules. Thus the engine that runs this "Debugger" is the mechanism for any MDriven system in the end. The run time part of the MDriven framework for turnkey is situated on the web server. As a fat WPF client or a thin client on Android, you will have the same engine that is mainly your model. Make sure you follow every rule that you have defined in the model.

It brings us to the main idea that defines what the MDriven catalog of products does, compared to other UML designers. We have the engine that actually executes the model. By implementing the model, you do not need to follow ordinary program code. Instead, the engine will follow this in runtime and never gets it wrong. Since it follows the model exactly, you can create anything - I want to stress this! Sometimes people say that "Well, you can't model everything", but that is a misconception - you can! And you should! However, you should only model things that are important to your business. As modeling is all about creating a model of reality, not actual reality, it helps you understand and cope with reality, not replace it. Keep in mind that you need to have a clear goal of what you want to achieve in order to be able to model. Otherwise, it will be hard to know what is important for your model.

What we have shown here is that it forced us to jump between the engine that executes the model and the actual model. In this way, it helps you get the model right. This auto-generated diagram also has cross-references. Thus, we have the option to find all the diagrams, where the "House" class is available.

For instance, we go to "Class1" and find that it is only available on “DiagramMyFirst". When you delete its attributes, they disappear from the repository as well. Save and go to the “OCLExecuteandDebug” window. We “re-read” the model and execute “allinstances”. As a result, the attributes are gone. This is a quick way to verify the ideas in your model that can actually be followed in real life.

Another simple way to execute the model is the realization that the engine will allow you a variety of options. The engine is like a software developer, made of software that gets your complete UML model specification, following it to the letter, never deviating one millimeter, and doing everything exactly as you want it to be. At this point, there is no need to hold back while designing something complex. You can be very explicit because there is no penalty in implementation - it is all about design. As an architect of the system, you own the model; rest assured that the engine will handle whatever you can express in the model. There are no limits to giving your best shot and really expressing yourself with this tool. What is more, the engine will handle it in a matter of seconds.

By all means, this changes the scene for software development when it comes to standard systems. Standard systems are standard models - something that anyone can do - a notion that is now uninteresting. The most efficient way is to come close to a specific business, thrive, and reduce the bottlenecks.

Pressing the play button brings up the "System prototype" that enables the "Debugger". Here we can see different kinds of persistence.

XML

XML is just to persist your data in an XML file that you choose on your hard drive. There's a simple way to keep your data c:\temp\mydata.xml. Starting the system with XML persistence, we bring up a new instance of the "Debugger". In case we create


House" in  “OCLExecuteandDebug”

action:

House.Create →”Execute”

We get the yellow “Save” button, as the engine has detected unsaved objects and that we are in a "persisted" environment. In case we select all the instances

House.allinstances    →   "Execute" * 3 times

We get three verifiable houses. The engine is working on the model and we are able to undo the actions that have been completed as well as save or cancel the edits altogether - that stands for the “Save”, “Undo”, “Redo” or “Cancel” options.

Setting "M1" to the first “Plan” for a new house from the resulting list, we might be allowed then to “Start Construction” - however, only to the extent that the house has an actual address.

M1  →    first.Address:="youraddress"   →   “Execute”

Finally, to engage the mechanism and lead it to the "Construction.GroundWork", the execution of the “Start Construction” stage is necessary. 

M1  →   first.StartConstruction → “Execute” →  "Save”

If we now make changes to the model, deleting some attributes e.g., and applying "re-read", the system will restart. Though by executing "House.allinstances" we do get data back, it has trimmed the things from the file that don't exist anymore.

As we dip further into this, we see that there is also an "SQL" option for the persistence and the configuration for that is more complex in order to define the connection string to a database. We need to be able to create the database. In any case, to keep and advance the data with changing models, we would press "EvolveDatabase" to save the script, which is a slightly more advanced option. The most considerable option is the "MDriven Server". Once we have this running server or a turnkey site, we can easily hook the "Debugger" up to it and observe our data with the cloud.

A very important piece to enable us to get further with our designs is to collect data from different views that belong together.

  • How should a user look at information?
  • How should it be consumed?

A Short Introduction to the "View Model" Concept.

Pressing the button to display the "View Model" Editor, we add a new one called "ViewModel1". It should be rooted in the class. For example, in "House" class, it would make sense to name it "HouseView". As a result, the "HouseView” appears in the repository, under the "ViewModel"s node as well.

"HouseView” has a few properties, which are easily changeable within the "ViewModel" Editor. We can define what information should be kept together for a specific use case. Usually, we would use the placing hints, but to prove a point, we will remove them for now. Think of the context of the "House", as if we had a house instance and our goal is to collect information about it.

Let’s say that the "Address" field is important:

(HouseView:House) Add column→ Address: String

It also has navigation to “The street”, so we could add a column for “The Street.Position”

Add column → TheStreet → TheStreet.Position: String

These were guided additions to the "ViewModel" that really helped, as we were in the context of the "House". However the "generic column" could be added as well.  This “New Column” doesn't have an OCL expression. For instance, in the OCL editor, we have the "Address" as a property

self.Address   where OCL "self" equals to the C# “this

In the "HouseView", we will call this column "Address", eliminating the duplicated one and renaming “The Street.Position” to “Position”.

At this point, it is apparent that this isn't going to cut it - I want something more - maybe I don't even want to have an "Address" on the "House" as a property because I have the "Street". This is what happens all the time when you model. If at one moment you think you got it, you soon realize you don't.

With the overview screen in, we add a condition that "TheStreet" has the name instead

Name: String?

and "House" has a "StreetNumber" that is also an integer:

StreetNumber: integer

Checking the whole model, there are a few errors, as the "DefaultStringRepresentation" for a "House" was set. This is no longer valid. Instead, using the OCL editor, we should go from "self.Address" to "TheStreet" and take the “name” as the attribute.

self.TheStreet.Name

What might be better for "DefaultStringRepresentation" in a "House":

self.TheStreet.Name+” “+self.StreetNumber.asstring

as "self.StreetNumber" is an integer, it needs to be treated as a string.

We still have a couple more errors. "Invalid Expression in Address self.Address 5: Address is not a member of "House" in the "HouseView". We get into the "HouseView" and discover the "Address" with the same problem.

self.TheStreet.Name

We had some errors left in the guard, in the state machine, “House state transition guard Plan -> Construction 9: Address” is not a member of the “House” to enable us to eliminate it with the same OCL expression editing. Instead, we do "TheStreet" which is a relation in this case and should not be empty.

not self.TheStreet → NotEmpty

This is what it means to be strongly typed. Whenever you break something, you get full information on what the side effects are. When you start collecting the use cases to think about what the data should be used for, it is easy to see where you lack careful thoughts for your model. This might be one use case, where you get the "Position" and the "Address". By adding "TheStreetNumber"  to the HouseView, we can find it automatically

Add column → TheStreetNumber → TheStreetNumber: Integer

You might have had a discussion about the "ModelView" and "ViewModel" patents and that's a good separation of concerns for keeping your business rules to the model and only section pieces of information for consuming certain use cases. Therefore, you can view it as each use case should have a specific view model to ensure that you don't overexpose information as is often the case when you expose a class. At this point, you get a lot of information and possibilities, but if you have really thought about the use case, you have redacted the available things from the class and followed a few associations to other important details. 

Since this is so centric to building views, we ask this question: "Why is it centric?" The data in the view needs a “ViewModel” to fulfill its presentation. Thus the ViewModel goes hand-in-hand with a use case that for its part, goes along with the view in most systems. We could add the "placing hints" to "ViewModel" to make this even easier.

Pointing out the "placing hints", expose the matrix that provides some metadata about each view model column, which states where it should be positioned in the grid. The "Show the gridlines" option displays a little grid that demonstrates how the engine interprets our view model and metadata for placing hints. We can go about drawing this, making it easy to design a few more details about the UI. This is not the actual UI that will be executed - it is just the definition of a way to collect data.

We re-read the model and select the "House"s and use the “Result as root ViewModel tree” option to show the data available according to the view. We could also have the engine construct a UI “Result as root ViewModel UI” based on the “ViewModel” definition and on the placing hints. This would be another way to set the value.

Head back to the "ViewModel" editor state - there's an action to construct "TheStreet". The next step will be adding a column prepared for Action.

Add column è. Add a column prepared for Action and now we get the action language. Using the expression for assigning the creation of a new street,

self.TheStreet:=Street.Create

the street object would be available to write in the action called e.g. "CreateStreet".

We can now return to the "Debugger" and re-read the model, but first, we save the changes. Select the "House" again → "Result as root ViewModel UI" and create the street.  We will be able to set the address and the position, finally saving it. Thus, looking at "Street.allinstances", we see that we created two streets.

This page was edited 5 days ago on 06/17/2024. What links here