Note: You don't need to worry about versioning if you don't care about backward compatibility. Specifically, Chrome is updated atomically, therefore there is no need to consider versioning for Mojo interfaces used internally between different components (blink, network, etc.).
Services extend their interfaces to support new functionalities over time and clients try to use those new functionalities when they are available. If services and clients are not updated at the same time, it is important for them to be able to communicate with each other using different snapshots (versions) of interfaces.
This document shows how to extend Mojo interfaces in a backward-compatible way. Changing interfaces in a non-backward-compatible way is not discussed, because in that case communications using different snapshots of interfaces are not possible anyway. You can make whatever changes to interfaces you want.
You can use the MinVersion=k attribute to indicate from which version a field is introduced. Assume you have the following struct:
And you would like to add a birthday field to it, you can do:
By default fields belong to version 0. New fields should be appended, with the MinVersion attribute set to a number greater than existing versions. “Append” here means the ordinal numbers of existing fields should not be changed.
What is ordinal number? Ordinal numbers determine memory layout of structs and also used as method IDs for interfaces. Implicitly, ordinal numbers are assigned to fields according to their position. In the example above, employee_id’s ordinal number is 0, name is 1. You can explicitly specify ordinal numbers:
You can reorder fields if you want, but you will have make sure those of existing fields unchanged. For example, the following struct is also backward-compatible:
Please also note that if a newly-added field is Mojo object or handle, it must be nullable. (The “?” suffix. Please see more details about nullable types here.)
Assume old_app is built against Employee version 0 while new_app is built against Employee version 1. When old_app passes an Employee struct to new_app, the birthday field is set to the default value (null in this case). When an Employee struct is passed in the opposite direction, the birthday field is silently discarded. If you do pass new version structs to where old version definitions are used, you must design APIs carefully so that discarding fields does not result in semantic difference or confusion!
There are two dimensions to extend a Mojo interface:
Parameter lists are treated as Mojo structs internally. So the rules of extending structs apply to parameter lists, too. The only difference is that the version number is scoped to the whole interface, instead of individual parameter lists.
Please note that adding callback to a method which doesn’t have callback previously is non-backward-compatible.
Similarly, you can reorder methods with ordinal numbers explicitly specified, as long as the ordinal numbers of existing methods are not changed.
Similar to structs, when you pass the parameter list of a method call/callback to someone using an old version, unrecognized fields are silently discarded. However, if the method call itself is not recognized, it is considered a fatal error and causes the message pipe to be closed. For example, when an app makes an AttachFingerPrint() call to another app built against HumanResourceDatabase version 0, the underlying message pipe is closed.
In C++ bindings, InterfacePtr<> defines the following methods to query/assert interface version:
You can find similar methods in other language bindings.
By default, enums are not extensible, which means they are not supposed to be extended in the future. When the bindings see incoming unknown values for non-extensible enum types, it will automatically close the pipe. Users don’t have to manually check received enum values.
If you want an enum to be extensible in the future, you should add [Extensible] attribute:
And later you can extend it without breaking backward compatibility:
With extensible enums, you may receive unknown enum values and you are responsible to handle that gracefully. Assume old_app is built against Department version 0 while new_app is built against Department version 1. During development of old_app, you should keep in mind that the app may receive unknown values for Department. Otherwise, when new_app sends RESEARCH to old_app, things will break. There is an auto-generated helper function to help you:
As you can see, [MinVersion=1] here is really just for documentation purpose. It doesn’t make a behavioral difference if you build new_app against:
Their names actually don’t matter at all. As long as the ordinal number (for methods and struct fields) or value (for enums) remains the same, you can modify the name without breaking backward compatibility. Therefore, say you would like to deprecate the method AddEmployee() in HumanResourceDatabase, you can simply rename the method:
Then at the service side you can provide a dummy/error-reporting implementation for it.