NSX Metamodels Progress Update
Introduction
As can be seen from the number of minor releases, nsx-metamodels is undergoing heavy construction work. This blogpost aims to clarify the reasoning behind the recent changes, the evolution of the metamodelling practice, and what is still ahead of us.
Please note that this post is quite the heavy read, but breaking down these complex concepts is necessary to explain where we are heading. It is primarily intended for those who have been working with metamodels, as well as anyone considering adopting them, with the objective of being informed of the process.
The most important terminology used in this post:
- Program
- ProgramType
- Module
- ModuleType
- M2 model: Refers to a meta-model, ontology or Domain Specific Language, specified in an M3.
- M1 model: Refers to an actual model, i.e., an instantiation of an M2 meta-model or ontology.
nsx-metamodels: a walk through the latest versions
2026.2.0 - the <dependencies> system
From nsx-metamodels 2025.15.0 until 2026.2.0, support for the <dependencies> system was developed.
Before 2025.15.0, it was not possible to add modules to an already existing ProgramType. If you wanted to add modules
to a Program, you had to create an extra ProgramType to support those modules. A new ProgramType entailed its own
custom ProgramFactory, which
brought a whole new set of issues. The developed <dependencies> system allows you to add modules to a Program and
its existing ProgramType without the significant amount of custom code needed before.
An explanation of this system using the messaging-metamodel example:
Messaging Ontology
MessagingJee Ontology
messagingontology: defines the standaloneMessageSpecificationmodule containing topics and schemas.messagingJeeontology: bridges elements and messaging. It definesMessageGroup(containingMessageProducersandMessageConsumerspublishing/subscribing to topics) which has an extension reference toelements::Component. This allows a JEE application to contain multiple message groups.
On the M1 level, a jeeApplication (or the specific component) needs to be able to read the target
MessageSpecification to relate its producers/consumers to actual topics and payloads. This is achieved via the
<dependencies> system:
<component name="messageBus" type="elements::Component">
<version>1.0.0</version>
<fullName>messageBus</fullName>
<dependencies>
<dependency>
<to>local::specifications/events/model/events.xml</to>
<scope>expansion</scope>
</dependency>
</dependencies>
</component>
This way, the MessageBus component (a Module) can import the "events" specification and use its topics and schemas.
On the M2 level, there is no direct reference between MessageSpecification and Component. The
<dependencies> system doesn’t mirror the M2 references 1:1. Instead, it defines where module imports are required to
access the information needed to model these M2 references on M1. References are thus defined between elements within
modules, dependencies define imports between modules.
What was wrong with this <dependencies> system?
The original <dependencies> system suffered from three main architectural shortcomings:
- Tightly coupled to
elements: Since the system was defined inside the elements ontology, creating a truly standalone metamodel was impossible without importingelements, defeating the goal of modularity. - Lack of transparency: the system was deeply buried within the
elementscore, making it obscure and difficult to understand. Besides, “dependency” is a highly overused term, lacking semantic meaning here. - Inability to depend on program: maybe most critically, the system could not resolve M1 level dependencies pointing
to a
Program. While an M2 reference to anElementClasswith aprogramrole was possible, it failed to interpret on M1. This prevented us from creating a high level connection between two metamodels.
2026.3.0 - the Standalone Metamodel profile
Enter version 2026.3.0. This was the first round of improvements on the nsx-metamodels to deal with both the issues
encountered before the <dependencies> system, and the issues of the <dependencies> system itself.
When using 2026.3.0, the following matters are changed as a way of improving the concerns:
- A standalone metamodel profile: A new profile was added to 2026.3.0:
Standalone Metamodel. This profile is almost identical toStandard Metamodel 3. However, in this profile, the tag#meta.dependencyDeclarationwas left out. This tag added an association reference toelements::Dependencyon every Program and Module in the metamodel, enforcing an ontologyImport to elements. Thanks to this profile, a standalone metamodel is truly standalone. - Programs can now be referenced to, and depended on: This version allows you to depend on Programs from another ontology. To do so, you define the program as a ModuleType, and thus import it into your model as a module instead of a program. This way, you can reference from program & module to Program on M2 level, and actually resolve this reference on M1 level.
An example:
<dataResource type="expansionControl::ModuleType">
<moduleType>
<name>NodeBackend</name>
<component>DoleComs</component>
<programType>DoleComs::NodeProgram</programType>
<expandableElement>elements::Application</expandableElement>
<moduleFactory>net.democritus.elements.ApplicationModuleFactory</moduleFactory>
<modelDirectory>$source.rootDirectory$/applications/$application.shortName$/model</modelDirectory>
<sourceDirectory><!-- NO HARVEST --></sourceDirectory>
<expansionDirectory><!-- NO EXPAND --></expansionDirectory>
<scope>reference</scope>
</moduleType>
</dataResource>
Here, we load elements::Application (a Program) as a Module into a different metamodel, with a different Program.
And we can then use it like this:
<node>
<name>MessagingNodeExample</name>
<packageName>com.dolecoms.node</packageName>
<sourceType>JAVA</sourceType>
<dependencies>
<dependency>
<to>local::applications/jeeApp/model/jeeApp.xml</to>
<scope>reference</scope>
</dependency>
</dependencies>
</node>
However, as you can see in the example, version 2026.3.0 still relies on the <dependencies> system to perform the
imports on M1 level.
2026.4.0 - Introducing ModuleImports
In the next version (2026.4.0) we introduce the ModuleImports. This mechanism improves the modularity of metamodels
and the transparency of connecting modules. Module imports are defined on a Program or Module, and specify which
other modules are required to make a Program or Module work. A technical deep dive on the topic can be found here.
Back to our MessageBus example, this is now changed to:
<component
xmlns="https://schemas.normalizedsystems.org/xsd/elements/2025/14/0"
moduleId="components/messageBus/model/messageBus"
type="elements::Component">
<import module="local::specifications/events/model/events.xml" scope="expansion"/>
<name>messageBus</name>
<version>1.0.0</version>
<fullName>messageBus</fullName>
<options>
<configuration.properties/>
</options>
</component>
Every import here specifies the import of a module, to be used in this module (a component called MessageBus).
Here, dependencies are merely interchanged by module imports. However, we can now as well couple programs:
<node xmlns="https://schemas.normalizedsystems.org/xsd/DoleComs/1/2/0-SNAPSHOT">
<import module="local::applications/jeeAppExample/model/jeeAppExample.xml" scope="reference"/>
<name>EmsNode</name>
<packageName>com.dolecoms.node</packageName>
<sourceType>JAVA</sourceType>
</node>
The program Node imports the program Application, an import that used to be impossible.
What is to be expected? What is the final aim of these reworks?
During these efforts to further modularize the metamodels and general project setups, we were able to identify the root cause of our pain points. Our tooling (µRadiant) thus far assumed a strong coupling between the functional and structural (or construction) information.
We consider the functional view to be the what, the system's operational behavior and functionality, i.e. our functional model (think data elements, task elements, etc.), whereas the construction view embodies the how, the system's physical structure and implementation (think expansions, layers, technologies, etc.).
The final aim of this effort is to decouple these two and create a truly modular way of project composition. In order to achieve this, some more sub-objectives need to be built on top of the groundwork we have laid in 2026.3.0 and 2026.4.0:
- Enable model loading without defining ProgramTypes or ModuleTypes, since these are only needed for expansion. With this, we can reference across modules and programs of various metamodels, and actually instantiate these references on M1 level without the technical worry of creating moduleTypes.
- Make Programs and Modules truly standalone blocks. We already don’t need the elements ontologyImport anymore, and they can already import one another using module imports. However, to establish its usability, µRadiant support is required.
- ExpansionSettings should be configurable parallel to the model, not as the root of it. There will be a duality of architecting the functional model, and designing the project / expansion composition.
- The core of our framework (prime-core) currently always includes the
elementsontology. As we increase modularization internally we are making the necessary steps to eventually extract elements from our core and provide it as a separate building block.
What version should I be using now?
We aim to provide a solid experience for these features, especially in our tools. While it is perfectly fine to use the latest release, you should know these new features (ModuleImports and importing Programs) are not yet fully supported in the µRadiant. So we recommend waiting to use these until the total effort is completed. The general advice would be to stick to the lowest version supporting the features you need.
However, in case these modular constructions are necessary, we ask you to tread carefully with the intermediate releases. Version 2026.4.0 does provide stable support for these new features, but lacks µRadiant support and requires some technical deliberation. Technical documentation can be found here.
