Overview
As new functionality is added to the platform and new features are added to the generated source code, source code generated by ADM changes as well. There are several facets to generated ADM source that have compatibility impacts to consider:
- ADM generated source depends on platform APIs that may not exist in earlier versions.
- ADM generated source depends on imported class that are potentially generated with an earlier version of ADM.
- ADM objects are serialized between different versions of the platform, so there are wire format considerations.
- The application's ADM model evolves over time (new types and fields may be added or removed).
This sections discusses how the compatibility is enforced.
Compatibility Goals
Wire Compatibility
Wire compatibility refers to the ability to be able to deserialize messages and entities off the wire or from disk between different versions.
- Within a major version of X wire compatibility is maintained unless there are extraordinary circumstances.
- So regardless of API compatibility level in the generated code it should always be possible to deserialize messages that were generated and serialized with the same major version.
- Note, however, that wire compatibility is contingent on the application model remaining compatible (e.g. fields not removed and field ids not changed). This topic is discussed in Modeling Message and State
API Compatibility
API compatibility refers to the ability of applications built using a particular version of the platform to be compiled, without error, on another version of the platform i.e. whether a code change is needed when migrating the application between platform versions.
- Within a major version of X API compatibility is maintained. However, some API methods can be marked as deprecated across minor releases
- Across major versions, X API compatibility can be broken. However, this can only happen due to removal or methods marked as deprecated in previous major versions
Generated Code Compatibility
Generated code compatibility refers to the compatibility between code generated by one version of the platform, and its compatibility at runtime with another version.
- A best effort is made to maintain backwards generated code compatibility with code generated within the same major version so that a new version of the platform runtime can operate with code generated within that version.
- However:
- To allow introducing new functionality in the generated code sometimes dependencies on new platform APIs are introduced. In this case a minor generated code compatibility version is incremented. This means that newly generated code can not be used with older versions of the platform.
- In extreme cases such as critical bug fixes it is possible that API changes are required in the generated code API which necessitates a major increment which makes a newer version of the platform require newly generated code (older generated code cannot be used).
The ADM Compatibility Level
The ADM compatLevel is of the form [MAJOR].[MINOR].[PATCH]. This compatLevel is added to the AdmGenerated
annotation's place on ADM generated classes. This annotation is used determine if the generated code is compatible with the platform version being run. Note that the ADM COMPATIBILITY_LEVEL
is versioned and tracked independently of the platform version.
See also: the ADM Compatibility Matrix for a listing of the compatibility level changes.
AdmGenerated Annotation
To validate compatibility of the generated code with the version of the platform jars on which the generated classes are being used, an com.neeve.adm.runtime.annotations.ADMGenerated annotation is added to ADM generated types. The ADMGenerated annotation has runtime retention and is thus available for introspection by applications at runtime.
Generated Code
AdmGenerated Properties
AdmGenerated Property | Description |
---|---|
encoding | For types generated with a particular encoding, this will be set to the encoding used. |
admVersion | Set to the version of the platform binary that was used to generate the code. |
date | The date and time at which the code was generated |
buildInfo | This can be supplied to the ADMGenerator at build time allowing the user to insert information about their build version. This can be done by supplying -b on the AdmCodgenerator command line. When using the maven plugin the <buildInfo></buildInfo> configuration property can be used to provide custom buildInfo other than default <artifactId>-<version> (tool) format. |
The ADMGenerated annotation is used by the platform as follows
- When a message factory is registered with the platform it's compatLevel will be examined to ensure that it is runtime compatible with the platform. The platform will increase it's runtimeCompatibilityVersion each time a change is made that would break compatibility with previously generated code. See Compatibilty Level Rules below.
Trace can be enabled to display the version of the platform that generated the factory when it is registered with the runtime to assist with support issues (nv.sma.trace=verbose or nv.ods.trace=verbose).
Sample output from this trace is:- It is also possible for the application to call MessageViewFactoryRegistry.dumpFactoryVersionInfo(StringBuilder) or StoreObjectFactoryRegistry.dumpFactoryVersionInfo(StringBuilder) at any time to collect the above trace.
Runtime Compatibilty Check
The compatibility rules are enforced as follows:
At runtime when Factories are registered with the platform their compatibility will be checked, and an EAdmCompatibilityException will be thrown if the classes are found to be incompatible.
Though not recommended, it is possible to disable compatibility checks by setting -Dnv.adm.disablecompatcheck=true
Bump in patch version
For the same major and minor versions, two differing patch versions must be API compatible at runtime even if the patch version differs. This relation holds true for platform code compiled as 2.0.0 and code generated by a later version of the platform at 2.0.1. The older platform code must be able to operate with the newer platform code.
A bump in patch version may be used by a newer version to indicate that the generated code supports additional APIs or capabilities. For example, perhaps in 2.0.1 messages are generated to implement new formatting in its Json representation or with some new accessor apis that are available by reflection.
Generated 2.0.0 | Generated 2.0.1 | |
---|---|---|
Platform 2.0.0 | ![]() | ![]() |
Platform 2.0.1 | ![]() | ![]() |
Bump in minor version
A bump in minor version indicates that newly generated code is not api compatible with previous versions of the platform, but that a newer version of the platform can still operate with previously generated code with the same MAJOR version.
An example of such a case would be when code generated with version 2.0.1 relies on a new platform APIs that is not available in previous versions of the runtime and would thus fail with a NoSuchMethodException
when operating with an older version of the platform. Another example of this case would be if the newer version of the code relies on some semantic behavior change in the existing platform API that would not be provided by an older version of the platform.
Generated 2.0.0 | Generated 2.1.0 | |
---|---|---|
Platform 2.0.0 | ![]() | ![]() |
Platform 2.1.0 | ![]() | ![]() |
Sample Compatibility Error
This error would be thrown running with a older version of the platform that is not compatible with a newer version's generated source (version numbers are hypothetical):
Bump in major version
A bump in major version means that generated code is not backwards or forwards compatible with the platform. A newer version of the platform can't operate with code generated with an older major version and an older version of the platform can't operate with a newer version of generated code.
An example of this case would be where there are breaking API changes in the generated API, for example 3.0.0 might introduce a new method.
Generated 2.0.0 | Generated 3.0.0 | |
---|---|---|
Platform 2.0.0 | ![]() | ![]() |
Platform 3.0.0 | ![]() | ![]() |
This error would be thrown running with a new version of the platform that is not compatible with older generated source (version numbers are hypothetical):
Versioning Recommendations
This section provides recommendations on how to organize your projects' code generation to ensure that they can use the latest and greatest ADM generated source code while remaining interoperable.
Small Projects
For small projects that are not versioned independently and are all expected to operated against the same Talon version, then regenerating ADM models with each release is the best way to ensure that your generated models are API compatible with the runtime.
Large Projects
For larger organizations that have applications that are versioned independently and upgrade X Platform version in a staggered fashion, it can be a challenge if those applications share the same models while on different X versions. There are 2 approaches to handling this situation:
Strategy 1 - Each Project Builds Private Models
If you have relatively few models, simply having each project (micro-app) generate and build its own private copy of the generated ADM source code is a simple approach to ensure that each project can use ADM code generated with their target platform version. It can break down if your organization has a lot of shared models and Stategy 2 below may be more appropriate.
Strategy 2 - Centrally build platform qualified Models
In this approach ADM models that are shared by multiple projects are built and managed as independently versioned artifacts. The generated ADM source code is generated against each platform version to be used by the organization as a whole and the resulting artifact is qualified with the X Platform version so that the resulting artifact reflects both the model version and the platform version from which it was generated.
Take for example an ADM Model called common-messages share by Project A and Project B that are both initially using X Platform 3.1.104, but will be independently upgrading to X 3.2.121.
common-messages v1.0
<model namespace= "com.example" defaultFactoryId= "1" > <messages> <message name= "MyMessage" factoryid= "1" id= "1" > <field name= "myIntField" type= "Integer" doc= "A sample integer field" /> </messsage> </messages> </model> |
And then the model is changed to add a new long field:
common-messages v1.1
<model namespace= "com.example" defaultFactoryId= "1" > <messages> <message name= "MyMessage" factoryid= "1" id= "1" > <field name= "myIntField" type= "Integer" /> <field name= "myLongField" type= "Long" /> </messsage> </messages> </model> |
Under this scheme the common-messages would be built and packages as:
- common-messages-1.0-adm3.1.104.jar
- common-messages-1.1-adm3.1.104.jar
- common-messages-1.0-adm3.2.121.jar
- common-messages-1.1-adm3.2.121.jar
The above packaging allows each project to independently upgrade to either the model version or a compatible X version as appropriate. If Project B upgrades to platform version 3.2.121 it could do the following:
- continue to use common-messages-1.0-adm3.1.104.jar:
- It won't get the new long field in v1.1 model. When it receives a 'MyMessage' serialized by the v1.1, version myLongField will be invisible.
- It won't get the enhancement for detecting stale enums in the 3.2.121 generated code.
- switch to common-messages-1.1-adm3.1.104.jar:
- If will pick up the new long field in v1.1. It can exchange 'MyMessage' with Project A, but Project A won't see the value of myLongField until it upgrades to a 1.1 version of the model.
- It will not get the platform enhancements in the generated code.
- switch to common-messages-1.0-adm3.2.121.jar: If Project B isn't ready for v1.1 of the model, but want the platform enhancements in generated code.
- It won't get the new long field in v1.1 model. When it receives a 'MyMessage' serialized by the v1.1 version, myLongField will be invisible.
- It will get platform enhancements in the generated code.
- switch to common-messages-1.1-adm3.2.121.jar:
- If will pick up the new long field in v1.1. It can exchange 'MyMessage' with Project A, but Project A won't see the value of myLongField until it upgrades to a 1.1 version of the model.
- It will get platform enhancements in the generated code.