OCL Editor, system prototyper and ViewModel

Continue on introducing the MDriven Designer as a UML modelling tool that allows you to capture enough details to cover every aspect of a software system. From this series you will find out how to work with New OCL Debugger editor, system prototyper and View Model editor. We will re-create the model of "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 more comfortable, we set the main tags mentioned in the video to the right bar menu of this mini player. Choose the interesting subtitle on the list and immediately get to the exact theme navigation-itemplace in the video. Now you can pick any topic to be instructed 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

Text guidance of video

Continuing on the types of diagrams, we will talk about the additional one. Which we start working with by double clicking beside the name of the class, otherwise, choosing he 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, that it has relations to associations. So this is another way to walk around your complete model. As you can observe each element in the repository and you are not able to hide anything from view.

Explaining the default repository graphical display, coordination works in the way that 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 one and clicking an attribute, 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 randomed as a "String?", if we were to change this to "False" the "?" would disappear.

The “State” attribute having the “S” icon on it, so let's go back to the "state diagram", clicking the dialog box. Once reaching the "State diagram", on the Property Inspector dialog, we see that "State attribute" is "State”, in the ”House.State". Here we are able to change its name, 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 a 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" 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". But we will need a "trigger" to take it forward called "StartConstruction".

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

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

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”. The great thing about OCL is that it really uses the language from our model a lot and just add a few operators to work on the information that we have to define in the model. It means that if we take care of quality model and design everything according to expectations when we speak about these things. The rules get really easy to read, so 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 has 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 saying that it should have a “ReturnType” of type "String". Setting the parameters for trigger:

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

Using the Action Editor Body, now we can implement, what the logic in the method should be. Here we call 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”

Respond is "wrong" and if we were to save this model we see that we have an error in

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

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

That 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 of the model.

What if we use “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 actually 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.

Afterwards,we can't actually get to the “design surface”, however the "Prototyper" is the model we can close t and still have the “Debugger” available. “OCLExecuteandDebug” is operated with OCL expressions, in case, 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 works?

As long as there is an empty line between expressions, the debugger will think of it as 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”

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

In case we were to  execute “House.Create” nothing much happens except, there is a new row in "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”, that 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, it 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 be "Address". There's an operator from C# - "IsNullOrEmpty", checking strings in 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

But our rule stayed the same, instead. That doesn't really help much here, because this works on the model, that we had when having started the "Debugger".  Starting everything over with "re-read the model”, it will work on the current model, so if we execute the

"House.allinstances" – we have no "houses" in the “Result List”, that's because their persistence, that we chose to use was “none” -  it remembers nothing while starting over.

That's 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 to 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 with the Pascal or Delphi it will recognizable as the assignment operator ":=". Separating it from a simple "=", which is just equals. As this is 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 forced to the "Start" state for the "Region1" and there is no guard on the way from the hinitial point of the inner region, it will end up in "Construction.GroundWork".

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

We have learnt how to define rules and a model to work on, 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. 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. And make sure that 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 do, compared to other uml designers. We have the engine that actually executes the model. By implementing the model, you do not need following ordinary program code. Instead, the engine will follow this in runtime and never gets wrong. Since it just follows the model exactly, you can create anything and 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 modelling is all about doing a model of reality, not doing reality. It helps you to understand and cope with reality, not replace it. So keep that in mind that you need to have a clear goal, with 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 to jump between the engine that executes the model and the actual model. In his way, it helps you get the model right. This Auto generated diagram also has cross-references. Thus, we have an option to find all the diagrams, where the "House" class is available.

For instance, we go to "Class1" finding out, that it is only available on “DiagramMyFirst". Deleting its attributes, they disappear from the repository as well. Save that, and going 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 actually can be followed in real life.

Another simple way to execute the model is the engine will allow you variety of options. Engine that we have here is like a software developer made of software, so this software developer gets your complete uml model specification. Following it by the letter, never deviating one millimeter, doing everything exactly as you want. 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 and be assured that the engine will handle whatever you can express in the model. There is no limits on 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. Which is really a standard model - something that anyone can do, notion, that is not interesting any longer. The most efficient way is really coming close to a specific business, thriving and reducing bottlenecks.

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

XML is just to persist your data in a xml file that you choose on your hard drive. That's a simple way to keep your data c:\temp\mydata.xml. Starting 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 having not saved objects and that we are in a persisted environment. In case we select all the instances

House.allinstances    →   "Execute" * 3 times

Got three verifiable houses. So the engine is working on model and we are able to undo the actions that have been completed as well as save or cancel the edits all together. That stands for “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 “Start Construction” stage is necessary. 

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

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

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

A very important piece to get further with our designs is to collect data in different views that  belongs together.

  • ž  how should a user look at information?
  • ž  how should it be consumed?

Let's make a short introduction of the "View model" concept.

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

"HouseView” has a few properties, which easily changeable with the "ViewModel"- editor. Here we define what information should be kept together for a specific use case.  Commonly we will be using the placing hints, but just to prove a point we are to remove them for now. Think of the context of the "House", as if we had a house instance and our goal is to collect the 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.  And this “New Column” doesn't have an OCL expression. For instance, in the OCL editor 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. As well as rename “The Street.Position” to “Position”.

At this point, it gets apparent that this isn't really going to cut it I want something more maybe I don't even want to "Address" on the "House" as a property, because I had the "Street". This is what happens all the time when you model, in one moment you think you got it and another - you don't.

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

Name: String?

and "House" has a "StreetNumber" only that is also integer

StreetNumber: integer

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

self.TheStreet.Name

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

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

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

We still have a couple of more error "Invalid Expression in Address self.Address 5: Address is not a member of "House" in the "HouseView". Going for it, we get into the "HouseView" and have 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”. Able to eliminate it with the same OCL expression editing. Instead, we do "TheStreet" that's a relation in this case and it should not be empty.

not self.TheStreet → NotEmpty

Here is what it means to be strongly typed that whenever you break something, you get full information on what are the side effects. Starting collecting the use cases to actually think about what the data should be used for, it's really easy to see wherever you haven't given a careful thought to your model. So this might be one use case, where you get the "Position" and the "Address". Adding "TheStreetNumber"  to the HouseView as well we can find it automatically

Add column → TheStreetNumber → TheStreetNumber: Integer

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

Since this is so centric to building views we have that "Why is it centric?"

The data in the view needs a “ViewModel” to fulfill itss presentation. Thus theviewmodell goes hand-in-hand with a use case that for its part goes along with the view in most systems. So making this even easier, we could add the "placing hints" to "ViewModel".

Pointing out the "placing hints", expose the matrix that provides some metadata about each view model column. It states where it should be positioned in the grid. "Show the gridlines" option stands for displaying a little grid that demonstrates how the engine interpreting our view model and metadata for the placing hints. We can go about drawing this, making it easier to design a few more details about the UI. This isn't actually the UI that will be executed, it's just the definition of a way to collect data.

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

Heading way back to the "ViewModel" editor state, there's an action to construct "TheStreet". 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

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

Now we can return to the "Debugger" and re-read the model, but first of all,  need to have the changes saved. Selecting the "House"s again → "Result as root ViewModel UI" and now I can create the street.  At this point, we will be able to set the address and the position, finally saving it. Thus, taking a look at "Street.allinstances" we see that there are two streets that we created.

This page was edited 70 days ago on 02/10/2024. What links here