# 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.
# Deleting a related model
{
"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.