DEV Community

Cover image for Designing Models in JavaScript
Erwan Carriou
Erwan Carriou

Posted on • Edited on

Designing Models in JavaScript

My grand-mother was a brodeuse, french name for embroiderer. She made beautiful traditional clothes for my family that we keep preciously. Recently when cleaning her home, we found a treasure, the patterns she used for creating her clothes. She used to draw her patterns on big, large papers. We could clearly understand the kind of clothes she wanted to do by looking at the patterns.

I could stop myself making an analogy with our work. We avoid too many times this fundamental step of design when developing. We start coding before designing the model of our application just because we think the code is the model. And it is not. The code is just the fabric of which the application is made. By reading the code, we only could guess what model, patterns were used to create the application. Moreover, because we are human, because our way of thinking differ from one developer to another developer, we need also to understand the mental space organization that the developer had when she/he was coding. And it is not that easy.

Separation of concerns

That is why no matter the frameworks you use and the code you write, the most important is the model that you define to create your application. And this model do not have to be in the code. It has to be somewhere else, normalized in a human readable format. But what format? We could use UML, it is quite common now and there are many design tools that use it. But the problem is that it is not a format, it is a language. It is quite complex and not so human friendly when you need to understand a diagram. I know that UML diagrams can be saved in XMI format but it is for machine not human.

Create the schema of the model

Let’s find the format we need with an example. Lets say we want to create a model of a Jedi. A Jedi is defined by its first name and its last name, she/he has got a mother and a father, and can have children. Let’s say we put all these informations related to this model into a JSON file, we could have something like that:

{
  "_name": "Jedi",
  "firstName": "property",
  "lastName": "property",
  "mother": "link",
  "father": "link",
  "children": "collection"
}
Enter fullscreen mode Exit fullscreen mode

When reading this JSON, we easily understand that:

  • a Jedi has two properties, firstName and lastName,
  • a Jedi is linked to a mother and a father and
  • a Jedi has a collection of children.

This JSON is in fact the schema of our model.

Generate the model and extend it

It would be great to be able to define a model like that, right? Now we have this schema, let’s go further. And what if we could generate UML class diagrams from the model to get something like this:

model

In order to do that, we are missing some basic informations, like the type of the properties. How to do this with our file? By simply generating a complete model with default values from the schema and then edit it:

{
  "_name": "Jedi",
  "firstName": {
    "type": "any",
    "readOnly": false,
    "mandatory": false,
    "default": ""
  },
  "lastName": {
    "type": "any",
    "readOnly": false,
    "mandatory": false,
    "default": ""
  },
  "mother": {
    "type": "Component",
    "readOnly": false,
    "mandatory": false,
    "default": ""
  },
  "father": {
    "type": "Component",
    "readOnly": false,
    "mandatory": false,
    "default": ""
  },
  "children": {
    "type": ["Component"],
    "readOnly": false,
    "mandatory": false,
    "default": []
  }
}
Enter fullscreen mode Exit fullscreen mode

We have here all informations defined in the schema that we can edit in a human readable format. For example, for firstName we can set its type (that has for default value any which means that it can have any type) to string.

From this JSON it will be easy to generate a UML class diagram. We have a complete model to build the application.

Use the model at runtime

Now what about the code? In Model Driven Architecture approach, we generate code from the model, generally from UML diagrams. It is great, but not perfect because the model we have defined has higher risk to be desynchronized from the code. So how to avoid this? By simply using the model definition of the application at runtime. The application has to read the schema and the model that we defined to create the classes, methods and components of the model.

Yes, seems pretty cool, but how do I do to implement the methods if it is generated at runtime? To do that you need to think in systems. A method is just an action that the system does when reacting to an event

In our case, let’s say we need to have in Jedi a method to get her/his full name. So we will edit the schema to have something like this:

{
  "_name": "Jedi",
  "firstName": "property",
  "lastName": "property",
  "mother": "link",
  "father": "link",
  "children": "collection",
  "fullName": "method"
}
Enter fullscreen mode Exit fullscreen mode

We have added fullName method in the schema. So we will have in the generated model:

{
  "_name": "Jedi",
  "firstName": {
    "type": "any",
    "readOnly": false,
    "mandatory": false,
    "default": ""
  },
  "lastName": {
    "type": "any",
    "readOnly": false,
    "mandatory": false,
    "default": ""
  },
  "mother": {
    "type": "Component",
    "readOnly": false,
    "mandatory": false,
    "default": ""
  },
  "father": {
    "type": "Component",
    "readOnly": false,
    "mandatory": false,
    "default": ""
  },
  "children": {
    "type": ["Component"],
    "readOnly": false,
    "mandatory": false,
    "default": []
  },
  "fullName": {
    "params": [
      {
        "name": "param",
        "type": "any",
        "mandatory": false,
        "default": null
      }
    ],
    "result": "any"
  }
}
Enter fullscreen mode Exit fullscreen mode

By default fullName takes one optional parameter and can return a value of any type. Now let’s say we want to implement the method. How to do that? By simply adding a behavior when the fullName event is send:

// require the Jedi class
const Jedi = runtime.require('Jedi');
// add a behavior
Jedi.on('fullName', () => this.firstName() + ' ' + this.lastName());
Enter fullscreen mode Exit fullscreen mode

And when this event is send? When fullName method is called in the code. In fact this method has been generated from the model, and when we invoke it, it just send a synchronous fullName event. The existing behavior related to that event will be then executed.

Now if we change the model of the method, it will never override its behavior.

Conclusion

Here are the different steps to follow when designing a model:

process

  • Define your model at high level in a human readable format.
  • A model will be generated from that schema.
  • Edit the generated model to specify the types, default values, … of your model.
  • Use this model to create at runtime classes, methods and components of your model.
  • Architecture your application with an event-driven approach to add behaviors to the methods.
  • Use your model to generate UML class diagrams.

If you want to see a concrete use of this approach, you can have a look at two of my projects:

that use deeply this methodology. For that purpose I have created the MSON format (for Metamodel JavaScript Object Notation) to define models in a human readable format. But it is not the only possible implementation of this methodology, find the one that matches your needs.


Credits: cover image by Christian Kaindl.

Top comments (3)

Collapse
 
thedavidmeister profile image
David Meister

schema is something like this clojure.org/guides/spec :)

Collapse
 
picaron profile image
Pascal Chouinard

The link for System Designer is incorrect (points to System Runtime)

Collapse
 
ecarriou profile image
Erwan Carriou

Fixed. Thanks!