# Type Definitions Semantic Versioning

Type definitions are version controlled, and the versioning follows the semantic versioning practices. It allows for the model's continuous evolution while maintaining backward compatibility for existing clients.​​

When versioning an Information Model Type Definition, we are concerned with the backward compatibility between the new type and existing models represented in earlier versions of the type. Therefore, we list the individual changes which will require a minor or major version change depending on the element that changed in the type definition.

The semantic schema versioning applied to schemas shall be done given the following rules.

In a version number MAJOR.MINOR.PATCH, increment the:

  • MAJOR when you make a breaking change that will prevent interaction with any historical data,

  • MINOR when you make a schema change which will keep compatibility with existing model instances but instances might require (automatic) migration,

  • PATCH when you make a schema change that is compatible with all historical data​.

# Classification of changes

Changes in different type definition elements require a change in a different part of the version number. So, for example, removing a property will always be a major version change as it will break existing models. In contrast, adding a variable to the type is considered a minor version change as instances will not break.

Comment Addition Deletion Change
Attribute Definition Changes in Attributes section Patch Major
Attribute Patch

If mandatory:
- Minor (if default value exists)
- Major (else)
Major [1] a) Changing from default value to array: major; b) Adding to the constraint/array: patch; c) Removing from the constraint/array: major; d) Changing from array to default value: patch
DataType Definition - - Major
Values Definition [2] Patch Major Major
Value Definition [2] Patch Minor Minor
Property Patch

If mandatory: Minor (if default value exists); Major (else)
Major Minor
Unique Properties IMPORTANT NOTE: It is not possible to change the unique properties of a type definition even with a new major version - - -
Variable Minor Major Major - for change of the Variables dataType
Reference Patch Major Major - for changes of isHierarchical (false -> true), removing/changing the value of to : [].
Minor - else e.g. isHierarchical (true -> false). Changing isContainment. Adding something to to: [].
Method Patch Minor Minor - changes in parameters.
RelatedModels ​Changes in related models will only apply to newly instantiated instances, existing ones will not be touched. Patch Patch Patch - this relates to changes of the unique property mapping for related models.
BaseTypes Major Major Change of version of baseType propagates to corresponding change in type (e.g, baseType@1.1.0 -> baseType@1.2.0 results in type version change from 2.0.0 to 2.1.0)
Tags Toplevel tags array in TypeDefinition Minor Minor Minor

[1] The following semantics are applied when changing an attribute default value to a constrain (using the "unit" : ["F", "C"] notation).

[2] Values Definition and Value Definition apply to Map Type Definition only.

# Examples

To better understand how to interpret the table above, below you can find a simple type definition and a few cases of how we could update that type. Each case has an explanation of the version change.

Note that the examples are separate from each other, and each example assumes that we start with version 1.0.0 of a type.

Type Definition:

{
  "model": "abb.ability.device",
  "typeId": "abb.myType",
  "version": "1.0.0",
  "tags": ["exercise"],
  "properties": {
    "serialNumber": {
      "dataType": "string",
      "isMandatory": true
    },
    "owner": {
      "dataType": "string"
    },
    "productionDate": {
      "dataType": "string",
      "format": "date"
    }
  },
  "variables": {
    "speed": {
      "dataType": "number"
    }
  },
  "methods": {
    "start": {}
  },
  "references": {
    "connectedDevices": {
      "to": [
        {
          "type": "some.device@1"
        }
      ]
    }
  },
  "relatedModels": {
    "abb.ability.configuration": {
      "type": "some.configuration@1"
    }
  },
  "attributes": {
    "format": {
      "dataType": "string"
    }
  }
}

# Adding a new attribute

In order to maintain clarity and readability of the presented examples, only the parts of JSON files that are relevant to the discussed matter are included.

{
  "typeId": "abb.myType",
  "version": "1.0.1",
  "properties": {
    "owner": {
      "dataType": "string",
      "modificationsCount": 0
    },
    ...
  },
  "attributes": {
    "format": {
      "dataType": "string"
    },
    "modificationsCount": {
      "dataType": "integer"
    }
  },
  ...
}

There were two changes:

  • a new attribute called modificationsCount was added,
  • the new attribute was used with the owner property.

According to the table presenting the Classifications of changes, the following version change applies:

  • a new attribute definition - PATCH,
  • updated property with a new attribute - PATCH.

Since both changes qualify as PATCH, we've changed the 1.0.0 version string to 1.0.1.

# Adding a new property (non-mandatory)

{
  "typeId": "abb.myType",
  "version": "1.0.1",
  "properties": {
    "manufacturer": {
      "dataType": "string"
    },
    ...
  },
  ...
}

This time, just one change was made - a new property (manufacturer) was added. The PATCH version change is enough since it is not a mandatory property (it does not have isMandatory: true). Again, we end up with 1.0.1.

# Adding a new property (mandatory)

{
  "typeId": "abb.myType",
  "version": "2.0.0",
  "properties": {
    "manufacturer": {
      "dataType": "string",
      "isMandatory": true
    },
    ...
  }
  ...
}

Similarly, a new property, called manufacturer, was added. However, this time it is a mandatory property, meaning whenever a new instance of our type gets created, it must contain a value of that property. Considering the table with the classification of changes, we see that such a change is a MAJOR version change. Therefore, our version is 2.0.0.

Minor change

Note that we could've possibly made it with a MINOR version change (e.g. 1.1.0) if our new property had a default value provided. Depending on your case it might make sense or not.

{
  "typeId": "abb.myType",
  "version": "1.0.1",
  "relatedModels": {
    // Deleted
  }
  ...
}

This time we've deleted the only relatedModels entry we had. Such a change qualifies as a PATCH, according to the table. Our version string is now 1.0.1.

# Map Properties

The map data type deserves its own section with a few examples of how semantic versioning impacts properties of that type.

First of all, maps can be used in two ways:

  • with values being just primitive

    {
      ...
      "properties": {
        "foo": {
          "dataType": "map",
          "values": "string"
        }
      },
      ...
    }
    
  • with values being objects

    {
      ...
      "properties": {
        "foo": {
          "dataType": "map",
          "values": {
            "one": {
              "dataType": "string"
            },
            "two": {
              "dataType": "string"
            }
          }
        }
      },
      ...
    }
    

The latter case can be treated the same as changes to "normal" properties. Here's an example:

If we have the following type definition:

{
  "typeId": "some.type",
  "version": "1.0.0",
  "properties": {
    "foo": {
      "dataType": "map",
      "values": {
        "one": {
          "dataType": "string"
        }
      }
    }
  },
  ...
}

we could make the following change:

{
  "typeId": "some.type",
  "version": "1.0.1",
  "properties": {
    "foo": {
      "dataType": "map",
      "values": {
        "one": {
          "dataType": "string"
        },
        "two": {
          "dataType": "number"
        }
      }
    }
  },
  ...
}

We've added another field inside of map's structure. It's a PATCH level of change. Any change inside of the values field of a map definition is treated the same way as changes in the properties section of a type definition. The table at the top of this document reflects that. Just compare the rows for "Values Definition" and "Property" - they are pretty much the same ("Values" is a bit simpler since you cannot have mandatory fields in a map).

Another example could be a change from one form of a map to another:

Before:

{
  "typeId": "some.type",
  "version": "1.0.0",
  "properties": {
    "foo": {
      "dataType": "map",
      "values": "string"
    }
  },
  ...
}

After:

{
  "typeId": "some.type",
  "version": "2.0.0",
  "properties": {
    "foo": {
      "dataType": "map",
      "values": {     
        "type":   {
          "dataType": "string"
        }
      }
    }
  },
  ...
}

We've completely changed the shape of that map. You could actually treat it as if we have deleted the original foo property and added a completely new one with the same name. It's a MAJOR change.

# Summary

Semantic versioning has quite strict rules regarding modifications. It is a good thing since it protects you from unexpected breaking changes in your modeling. If a change is breaking, a MAJOR version bump needs to be made explicitly. Existing objects relying on older versions of your types will continue to function properly since any relation between two objects (related models or references) has information about the major version of the other type that is expected.

# References

Last updated: 2/11/2022, 10:32:55 AM
Feedback